merge fx-team to mozilla-central a=merge
|
@ -1,6 +1,7 @@
|
|||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- "iojs"
|
||||
- "0.10"
|
||||
|
||||
notifications:
|
||||
|
|
|
@ -17,12 +17,14 @@ If you have code that you'd like to contribute the Jetpack project, follow these
|
|||
3. Make your changes, per the Overview
|
||||
4. Write a test ([intro][test intro], [API][test API])
|
||||
5. Submit pull request with changes and a title in a form of `Bug XXX - description`
|
||||
6. Copy the pull request link from GitHub and paste it in as an attachment to the bug
|
||||
7. Each pull request should idealy contain only one commit, so squash the commits if necessary.
|
||||
8. Flag the attachment for code review from one of the Jetpack reviewers listed below.
|
||||
6. Make sure that [Travis CI](https://travis-ci.org/mozilla/addon-sdk/branches) tests are passing for your branch.
|
||||
7. Copy the pull request link from GitHub and paste it in as an attachment to the bug
|
||||
8. Each pull request should idealy contain only one commit, so squash the commits if necessary.
|
||||
9. Flag the attachment for code review from one of the Jetpack reviewers listed below.
|
||||
This step is optional, but could speed things up.
|
||||
9. Address any nits (ie style changes), or other issues mentioned in the review.
|
||||
10. Finally, once review is approved, a team member will do the merging
|
||||
10. Address any nits (ie style changes), or other issues mentioned in the review.
|
||||
|
||||
Finally, once review is approved, a team member will do the merging
|
||||
|
||||
## Good First Bugs
|
||||
|
||||
|
|
|
@ -81,14 +81,12 @@ function startup(reason, options) Startup.onceInitialized.then(() => {
|
|||
// Exports data to a pseudo module so that api-utils/l10n/core
|
||||
// can get access to it
|
||||
definePseudo(options.loader, '@l10n/data', data ? data : null);
|
||||
return ready;
|
||||
}).then(function() {
|
||||
run(options);
|
||||
return ready.then(() => run(options, !!data));
|
||||
}).then(null, console.exception);
|
||||
return void 0; // otherwise we raise a warning, see bug 910304
|
||||
});
|
||||
|
||||
function run(options) {
|
||||
function run(options, hasL10n) {
|
||||
try {
|
||||
// Try initializing HTML localization before running main module. Just print
|
||||
// an exception in case of error, instead of preventing addon to be run.
|
||||
|
@ -96,7 +94,7 @@ function run(options) {
|
|||
// Do not enable HTML localization while running test as it is hard to
|
||||
// disable. Because unit tests are evaluated in a another Loader who
|
||||
// doesn't have access to this current loader.
|
||||
if (options.main !== 'sdk/test/runner') {
|
||||
if (hasL10n && options.main !== 'sdk/test/runner') {
|
||||
require('../l10n/html').enable();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -340,7 +340,7 @@ function getPotentialLeaks() {
|
|||
let item = {
|
||||
path: matches[1],
|
||||
principal: details[1],
|
||||
location: details[2] ? details[2].replace("\\", "/", "g") : undefined,
|
||||
location: details[2] ? details[2].replace(/\\/g, "/") : undefined,
|
||||
source: details[3] ? details[3].split(" -> ").reverse() : undefined,
|
||||
toString: function() this.location
|
||||
};
|
||||
|
@ -364,8 +364,8 @@ function getPotentialLeaks() {
|
|||
|
||||
let item = {
|
||||
path: matches[1],
|
||||
location: details[1].replace("\\", "/", "g"),
|
||||
source: [details[1].replace("\\", "/", "g")],
|
||||
location: details[1].replace(/\\/g, "/"),
|
||||
source: [details[1].replace(/\\/g, "/")],
|
||||
toString: function() this.location
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ const { emit } = require("../event/core");
|
|||
const { WindowTracker, windowIterator } = require("../deprecated/window-utils");
|
||||
const { DOMEventAssembler } = require("../deprecated/events/assembler");
|
||||
const { Class } = require("../core/heritage");
|
||||
const { Cu } = require("chrome");
|
||||
|
||||
// Event emitter objects used to register listeners and emit events on them
|
||||
// when they occur.
|
||||
|
@ -42,6 +43,9 @@ const Observer = Class({
|
|||
* Keyboard event being emitted.
|
||||
*/
|
||||
handleEvent(event) {
|
||||
// Ignore events from windows in the child process as they can't be top-level
|
||||
if (Cu.isCrossProcessWrapper(event.target))
|
||||
return;
|
||||
emit(this, event.type, event.target, event);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -703,7 +703,8 @@ Loader.main = main;
|
|||
const Module = iced(function Module(id, uri) {
|
||||
return create(null, {
|
||||
id: { enumerable: true, value: id },
|
||||
exports: { enumerable: true, writable: true, value: create(null) },
|
||||
exports: { enumerable: true, writable: true, value: create(null),
|
||||
configurable: true },
|
||||
uri: { value: uri }
|
||||
});
|
||||
});
|
||||
|
@ -784,6 +785,7 @@ function Loader(options) {
|
|||
}
|
||||
}, modules);
|
||||
|
||||
const builtinModuleExports = modules;
|
||||
modules = keys(modules).reduce(function(result, id) {
|
||||
// We resolve `uri` from `id` since modules are cached by `uri`.
|
||||
let uri = resolveURI(id, mapping);
|
||||
|
@ -792,7 +794,16 @@ function Loader(options) {
|
|||
if (isNative && !uri)
|
||||
uri = id;
|
||||
let module = Module(id, uri);
|
||||
module.exports = freeze(modules[id]);
|
||||
|
||||
// Lazily expose built-in modules in order to
|
||||
// allow them to be loaded lazily.
|
||||
Object.defineProperty(module, "exports", {
|
||||
enumerable: true,
|
||||
get: function() {
|
||||
return builtinModuleExports[id];
|
||||
}
|
||||
});
|
||||
|
||||
result[uri] = freeze(module);
|
||||
return result;
|
||||
}, {});
|
||||
|
|
|
@ -60,6 +60,7 @@ DEFAULT_NO_CONNECTIONS_PREFS = {
|
|||
'browser.safebrowsing.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.safebrowsing.reportURL': 'http://localhost/safebrowsing-dummy/report',
|
||||
'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport',
|
||||
'browser.selfsupport.url': 'http://localhost/repair-dummy',
|
||||
'browser.trackingprotection.gethashURL': 'http://localhost/safebrowsing-dummy/gethash',
|
||||
'browser.trackingprotection.updateURL': 'http://localhost/safebrowsing-dummy/update',
|
||||
|
||||
|
|
|
@ -10,7 +10,8 @@ const { fromIterator } = require('sdk/util/array');
|
|||
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
|
||||
'menu_socialSidebar',
|
||||
'menu_historySidebar',
|
||||
'menu_bookmarksSidebar'
|
||||
'menu_bookmarksSidebar',
|
||||
'menu_readingListSidebar'
|
||||
];
|
||||
|
||||
function isSidebarShowing(window) {
|
||||
|
|
|
@ -129,8 +129,7 @@ exports.testDestroyEdgeCaseBugWithPrivateWindow = function(assert, done) {
|
|||
assert.pass('onShow works for Sidebar');
|
||||
loader.unload();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* 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";
|
||||
|
||||
exports.useFoo= function () {
|
||||
return require('foo');
|
||||
}
|
|
@ -17,6 +17,7 @@
|
|||
"browser.safebrowsing.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.safebrowsing.reportURL": "http://localhost/safebrowsing-dummy/report",
|
||||
"browser.safebrowsing.malware.reportURL": "http://localhost/safebrowsing-dummy/malwarereport",
|
||||
"browser.selfsupport.url": "http://localhost/repair-dummy",
|
||||
"browser.trackingprotection.gethashURL": "http://localhost/safebrowsing-dummy/gethash",
|
||||
"browser.trackingprotection.updateURL": "http://localhost/safebrowsing-dummy/update",
|
||||
"browser.newtabpage.directory.source": "data:application/json,{'jetpack':1}",
|
||||
|
|
|
@ -16,7 +16,8 @@ const { fromIterator } = require('sdk/util/array');
|
|||
const BUILTIN_SIDEBAR_MENUITEMS = exports.BUILTIN_SIDEBAR_MENUITEMS = [
|
||||
'menu_socialSidebar',
|
||||
'menu_historySidebar',
|
||||
'menu_bookmarksSidebar'
|
||||
'menu_bookmarksSidebar',
|
||||
'menu_readingListSidebar'
|
||||
];
|
||||
|
||||
function isSidebarShowing(window) {
|
||||
|
|
|
@ -506,4 +506,22 @@ exports["test Cu.import in b2g style"] = (assert) => {
|
|||
"Loader.Loader is a funciton");
|
||||
};
|
||||
|
||||
exports['test lazy globals'] = function (assert) {
|
||||
let uri = root + '/fixtures/loader/lazy/';
|
||||
let gotFoo = false;
|
||||
let foo = {};
|
||||
let modules = {
|
||||
get foo() {
|
||||
gotFoo = true;
|
||||
return foo;
|
||||
}
|
||||
};
|
||||
let loader = Loader({ paths: { '': uri }, modules: modules});
|
||||
assert.ok(!gotFoo, "foo hasn't been accessed during loader instanciation");
|
||||
let program = main(loader, 'main');
|
||||
assert.ok(!gotFoo, "foo hasn't been accessed during module loading");
|
||||
assert.equal(program.useFoo(), foo, "foo mock works");
|
||||
assert.ok(gotFoo, "foo has been accessed only when we first try to use it");
|
||||
};
|
||||
|
||||
require('sdk/test').run(exports);
|
||||
|
|
|
@ -29,6 +29,17 @@ exports.testGetAndSet = function(assert) {
|
|||
"preferences-service should read from " +
|
||||
"application-wide preferences service");
|
||||
|
||||
// test getting a pref that does not exist,
|
||||
// and where we provide no default
|
||||
assert.equal(
|
||||
prefs.get("test_dne_get_pref", "default"),
|
||||
"default",
|
||||
"default was used for a pref that does not exist");
|
||||
assert.equal(
|
||||
prefs.get("test_dne_get_pref"),
|
||||
undefined,
|
||||
"undefined was returned for a pref that does not exist with no default");
|
||||
|
||||
prefs.set("test_set_get_pref.integer", 1);
|
||||
assert.equal(prefs.get("test_set_get_pref.integer"), 1,
|
||||
"set/get integer preference should work");
|
||||
|
|
|
@ -96,9 +96,12 @@ exports.testSidebarBasicLifeCycle = function*(assert) {
|
|||
sidebar.destroy();
|
||||
sidebar.destroy();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
let id = mi.getAttribute('id');
|
||||
|
||||
if (BUILTIN_SIDEBAR_MENUITEMS.indexOf(id) < 0) {
|
||||
assert.fail('the menuitem "' + id + '" is not a built-in sidebar');
|
||||
}
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
|
||||
|
@ -360,8 +363,7 @@ exports.testSidebarUnload = function*(assert) {
|
|||
|
||||
loader.unload();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
|
@ -598,9 +600,12 @@ exports.testDestroyEdgeCaseBug = function*(assert) {
|
|||
yield sidebar.show();
|
||||
loader.unload();
|
||||
|
||||
let sidebarMI = getSidebarMenuitems();
|
||||
for (let mi of sidebarMI) {
|
||||
assert.ok(BUILTIN_SIDEBAR_MENUITEMS.indexOf(mi.getAttribute('id')) >= 0, 'the menuitem is for a built-in sidebar')
|
||||
for (let mi of getSidebarMenuitems()) {
|
||||
let id = mi.getAttribute('id');
|
||||
|
||||
if (BUILTIN_SIDEBAR_MENUITEMS.indexOf(id) < 0) {
|
||||
assert.fail('the menuitem "' + id + '" is not a built-in sidebar');
|
||||
}
|
||||
assert.ok(!isChecked(mi), 'no sidebar menuitem is checked');
|
||||
}
|
||||
assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
|
||||
|
@ -762,6 +767,9 @@ exports.testURLSetterToSameValueReloadsSidebar = function*(assert) {
|
|||
document = window.document;
|
||||
assert.pass('new window was opened');
|
||||
|
||||
yield focus(window);
|
||||
assert.pass('new window was focused');
|
||||
|
||||
yield sidebar1.show();
|
||||
|
||||
assert.equal(isShowing(sidebar1), true, 'the sidebar is showing');
|
||||
|
@ -1490,23 +1498,29 @@ exports.testShowHideRawWindowArg = function*(assert) {
|
|||
});
|
||||
|
||||
let mainWindow = getMostRecentBrowserWindow();
|
||||
let newWindow = yield open().then(focus);
|
||||
let newWindow = yield windowPromise(mainWindow.OpenBrowserWindow(), 'load');
|
||||
assert.pass("Created the new window");
|
||||
|
||||
yield focus(newWindow);
|
||||
assert.pass("Focused the new window");
|
||||
|
||||
yield focus(mainWindow);
|
||||
assert.pass("Focused the old window");
|
||||
|
||||
yield sidebar.show(newWindow);
|
||||
|
||||
assert.pass('the sidebar was shown');
|
||||
assert.ok(!isSidebarShowing(mainWindow), 'sidebar is not showing in main window');
|
||||
assert.ok(isSidebarShowing(newWindow), 'sidebar is showing in new window');
|
||||
assert.equal(isSidebarShowing(mainWindow), false, 'sidebar is not showing in main window');
|
||||
assert.equal(isSidebarShowing(newWindow), true, 'sidebar is showing in new window');
|
||||
|
||||
assert.ok(isFocused(mainWindow), 'main window is still focused');
|
||||
|
||||
yield sidebar.hide(newWindow);
|
||||
|
||||
assert.ok(isFocused(mainWindow), 'main window is still focused');
|
||||
assert.ok(!isSidebarShowing(mainWindow), 'sidebar is not showing in main window');
|
||||
assert.ok(!isSidebarShowing(newWindow), 'sidebar is not showing in new window');
|
||||
assert.equal(isFocused(mainWindow), true, 'main window is still focused');
|
||||
assert.equal(isSidebarShowing(mainWindow), false, 'sidebar is not showing in main window');
|
||||
assert.equal(isSidebarShowing(newWindow), false, 'sidebar is not showing in new window');
|
||||
|
||||
sidebar.destroy();
|
||||
}
|
||||
|
||||
|
@ -1547,6 +1561,7 @@ before(exports, (name, assert) => {
|
|||
});
|
||||
|
||||
after(exports, function*(name, assert) {
|
||||
assert.pass("Cleaning new windows and tabs");
|
||||
yield cleanUI();
|
||||
assert.equal(isSidebarShowing(), false, 'no sidebar is showing');
|
||||
});
|
||||
|
|
|
@ -5,10 +5,7 @@
|
|||
|
||||
const { Cu } = require('chrome');
|
||||
const { Loader } = require('sdk/test/loader');
|
||||
|
||||
function gc() {
|
||||
return new Promise(resolve => Cu.schedulePreciseGC(resolve));
|
||||
};
|
||||
const { gc } = require("sdk/test/memory");
|
||||
|
||||
exports['test adding item'] = function*(assert) {
|
||||
let loader = Loader(module);
|
||||
|
@ -121,31 +118,31 @@ exports['test adding non object or null item'] = function(assert) {
|
|||
assert.throws(() => {
|
||||
add(items, 'foo');
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, 0);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, undefined);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, null);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(items, true);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
};
|
||||
|
||||
|
@ -158,31 +155,31 @@ exports['test adding to non object or null item'] = function(assert) {
|
|||
assert.throws(() => {
|
||||
add('foo', item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(0, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(undefined, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(null, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
|
||||
assert.throws(() => {
|
||||
add(true, item);
|
||||
},
|
||||
/^value is not a non-null object/,
|
||||
/^\w+ is not a non-null object/,
|
||||
'only non-null object are allowed');
|
||||
};
|
||||
|
||||
|
|
|
@ -1336,7 +1336,7 @@ pref("browser.devedition.theme.showCustomizeButton", false);
|
|||
|
||||
// Developer edition promo preferences
|
||||
pref("devtools.devedition.promo.shown", false);
|
||||
pref("devtools.devedition.promo.url", "https://mozilla.org/firefox/developer");
|
||||
pref("devtools.devedition.promo.url", "https://www.mozilla.org/firefox/developer/?utm_source=firefox-dev-tools&utm_medium=firefox-browser&utm_content=betadoorhanger");
|
||||
|
||||
// Only potentially show in beta release
|
||||
#if MOZ_UPDATE_CHANNEL == beta
|
||||
|
@ -1438,6 +1438,9 @@ pref("devtools.timeline.enabled", true);
|
|||
#else
|
||||
pref("devtools.timeline.enabled", false);
|
||||
#endif
|
||||
|
||||
// TODO remove `devtools.timeline.hiddenMarkers.` branches when performance
|
||||
// tool lands (bug 1075567)
|
||||
pref("devtools.timeline.hiddenMarkers", "[]");
|
||||
|
||||
// Enable perftools via build command
|
||||
|
@ -1455,6 +1458,7 @@ pref("devtools.profiler.ui.show-platform-data", false);
|
|||
pref("devtools.profiler.ui.show-idle-blocks", true);
|
||||
|
||||
// The default Performance UI settings
|
||||
pref("devtools.performance.timeline.hidden-markers", "[]");
|
||||
pref("devtools.performance.ui.invert-call-tree", true);
|
||||
pref("devtools.performance.ui.invert-flame-graph", false);
|
||||
pref("devtools.performance.ui.flatten-tree-recursion", true);
|
||||
|
|
|
@ -688,7 +688,6 @@ let SessionStoreInternal = {
|
|||
Services.obs.notifyObservers(browser, NOTIFY_TAB_RESTORED, null);
|
||||
}
|
||||
|
||||
delete browser.__SS_restore_data;
|
||||
delete browser.__SS_data;
|
||||
|
||||
SessionStoreInternal._resetLocalTabRestoringState(tab);
|
||||
|
@ -2683,26 +2682,16 @@ let SessionStoreInternal = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Restores the specified tab. If the tab can't be restored (eg, no history or
|
||||
* calling gotoIndex fails), then state changes will be rolled back.
|
||||
* This method will check if gTabsProgressListener is attached to the tab's
|
||||
* window, ensuring that we don't get caught without one.
|
||||
* This method removes the session history listener right before starting to
|
||||
* attempt a load. This will prevent cases of "stuck" listeners.
|
||||
* If this method returns false, then it is up to the caller to decide what to
|
||||
* do. In the common case (restoreNextTab), we will want to then attempt to
|
||||
* restore the next tab. In the other case (selecting the tab, reloading the
|
||||
* tab), the caller doesn't actually want to do anything if no page is loaded.
|
||||
* Kicks off restoring the given tab.
|
||||
*
|
||||
* @param aTab
|
||||
* the tab to restore
|
||||
*
|
||||
* @returns true/false indicating whether or not a load actually happened
|
||||
* @param aLoadArguments
|
||||
* optional load arguments used for loadURI()
|
||||
*/
|
||||
restoreTabContent: function (aTab, aLoadArguments = null) {
|
||||
let window = aTab.ownerDocument.defaultView;
|
||||
let browser = aTab.linkedBrowser;
|
||||
let tabData = browser.__SS_data;
|
||||
|
||||
// Make sure that this tab is removed from the priority queue.
|
||||
TabRestoreQueue.remove(aTab);
|
||||
|
@ -2715,17 +2704,6 @@ let SessionStoreInternal = {
|
|||
browser.removeAttribute("pending");
|
||||
aTab.removeAttribute("pending");
|
||||
|
||||
let activeIndex = tabData.index - 1;
|
||||
|
||||
// Attach data that will be restored on "load" event, after tab is restored.
|
||||
if (tabData.entries.length) {
|
||||
// restore those aspects of the currently active documents which are not
|
||||
// preserved in the plain history entries (mainly scroll state and text data)
|
||||
browser.__SS_restore_data = tabData.entries[activeIndex] || {};
|
||||
} else {
|
||||
browser.__SS_restore_data = {};
|
||||
}
|
||||
|
||||
browser.messageManager.sendAsyncMessage("SessionStore:restoreTabContent",
|
||||
{loadArguments: aLoadArguments});
|
||||
},
|
||||
|
|
|
@ -162,7 +162,9 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, {
|
|||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: snapshotItem }) {
|
||||
if (!snapshotItem) {
|
||||
// Check to ensure the attachment has an actor, like
|
||||
// an in-progress recording.
|
||||
if (!snapshotItem || !snapshotItem.attachment.actor) {
|
||||
return;
|
||||
}
|
||||
let { calls, thumbnails, screenshot } = snapshotItem.attachment;
|
||||
|
|
|
@ -4,6 +4,7 @@ support-files =
|
|||
doc_raf-begin.html
|
||||
doc_settimeout.html
|
||||
doc_no-canvas.html
|
||||
doc_raf-no-canvas.html
|
||||
doc_simple-canvas.html
|
||||
doc_simple-canvas-bitmasks.html
|
||||
doc_simple-canvas-deep-stack.html
|
||||
|
@ -44,7 +45,9 @@ skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in
|
|||
[browser_canvas-frontend-reload-02.js]
|
||||
[browser_canvas-frontend-slider-01.js]
|
||||
[browser_canvas-frontend-slider-02.js]
|
||||
[browser_canvas-frontend-snapshot-select.js]
|
||||
[browser_canvas-frontend-snapshot-select-01.js]
|
||||
[browser_canvas-frontend-snapshot-select-02.js]
|
||||
[browser_canvas-frontend-stepping.js]
|
||||
[browser_canvas-frontend-stop-01.js]
|
||||
[browser_canvas-frontend-stop-02.js]
|
||||
[browser_canvas-frontend-stop-03.js]
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests if selecting snapshots in the frontend displays the appropriate data
|
||||
* respective to their recorded animation frame.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, panel } = yield initCanvasDebuggerFrontend(SIMPLE_CANVAS_URL);
|
||||
let { window, $, EVENTS, SnapshotsListView, CallsListView } = panel.panelWin;
|
||||
|
||||
yield reload(target);
|
||||
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
let snapshotTarget = SnapshotsListView.getItemAtIndex(0).target;
|
||||
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, snapshotTarget, window);
|
||||
|
||||
ok(true, "clicking in-progress snapshot does not fail");
|
||||
|
||||
let finished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
yield finished;
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that a recording that has a rAF cycle, but no draw calls, fails
|
||||
* after timeout.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, panel } = yield initCanvasDebuggerFrontend(RAF_NO_CANVAS_URL);
|
||||
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
|
||||
|
||||
yield reload(target);
|
||||
|
||||
let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
|
||||
yield recordingStarted;
|
||||
|
||||
is($("#empty-notice").hidden, true, "Empty notice not shown");
|
||||
is($("#waiting-notice").hidden, false, "Waiting notice shown");
|
||||
|
||||
let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
|
||||
|
||||
yield promise.all([recordingFinished, recordingCancelled]);
|
||||
|
||||
ok(true, "Recording stopped and was considered failed.");
|
||||
|
||||
is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
|
||||
is($("#empty-notice").hidden, false, "Empty notice shown");
|
||||
is($("#waiting-notice").hidden, true, "Waiting notice not shown");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Canvas inspector test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
function render () { window.requestAnimationFrame(render); }
|
||||
window.requestAnimationFrame(render);
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -30,6 +30,7 @@ const FRAME_SCRIPT_UTILS_URL = "chrome://browser/content/devtools/frame-script-u
|
|||
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/";
|
||||
const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
|
||||
const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
|
||||
const RAF_NO_CANVAS_URL = EXAMPLE_URL + "doc_raf-no-canvas.html";
|
||||
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
|
||||
const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
|
||||
const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
|
||||
|
|
|
@ -14,6 +14,11 @@
|
|||
min-width: 100%;
|
||||
}
|
||||
|
||||
body.dragging .tag-line {
|
||||
cursor: grabbing;
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
#root-wrapper:after {
|
||||
content: "";
|
||||
display: block;
|
||||
|
@ -63,6 +68,40 @@
|
|||
padding-left: 1001em;
|
||||
}
|
||||
|
||||
/* Normally this element takes space in the layout even if it's position: relative
|
||||
* by adding height: 0 we let surrounding elements to fill the blank space */
|
||||
.child.dragging {
|
||||
position: relative;
|
||||
pointer-events: none;
|
||||
opacity: 0.7;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* Indicates a tag-line in the markup-view as being an active drop target by
|
||||
* drawing a horizontal line where the dragged element would be inserted if
|
||||
* dropped here */
|
||||
.tag-line.drop-target::before, .tag-line.drag-target::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-line.drag-target::before {
|
||||
border-top: 2px dashed var(--theme-contrast-background);
|
||||
}
|
||||
|
||||
.tag-line.drop-target::before {
|
||||
border-top: 2px dashed var(--theme-content-color1);
|
||||
}
|
||||
|
||||
/* In case the indicator is put on the closing .tag-line, the indentation level
|
||||
* will become misleading, so we push it forward to match the indentation level */
|
||||
ul.children + .tag-line::before {
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.tag-line {
|
||||
min-height: 1.4em;
|
||||
line-height: 1.4em;
|
||||
|
|
|
@ -14,6 +14,10 @@ const COLLAPSE_ATTRIBUTE_LENGTH = 120;
|
|||
const COLLAPSE_DATA_URL_REGEX = /^data.+base64/;
|
||||
const COLLAPSE_DATA_URL_LENGTH = 60;
|
||||
const NEW_SELECTION_HIGHLIGHTER_TIMER = 1000;
|
||||
const GRAB_DELAY = 400;
|
||||
const DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE = 50;
|
||||
const DRAG_DROP_MIN_AUTOSCROLL_SPEED = 5;
|
||||
const DRAG_DROP_MAX_AUTOSCROLL_SPEED = 15;
|
||||
|
||||
const {UndoStack} = require("devtools/shared/undo");
|
||||
const {editableField, InplaceEditor} = require("devtools/shared/inplace-editor");
|
||||
|
@ -62,6 +66,7 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
|||
this._inspector = aInspector;
|
||||
this.walker = this._inspector.walker;
|
||||
this._frame = aFrame;
|
||||
this.win = this._frame.contentWindow;
|
||||
this.doc = this._frame.contentDocument;
|
||||
this._elt = this.doc.querySelector("#root");
|
||||
this.htmlEditor = new HTMLEditor(this.doc);
|
||||
|
@ -94,6 +99,9 @@ function MarkupView(aInspector, aFrame, aControllerWindow) {
|
|||
|
||||
this._onMouseClick = this._onMouseClick.bind(this);
|
||||
|
||||
this._onMouseUp = this._onMouseUp.bind(this);
|
||||
this.doc.body.addEventListener("mouseup", this._onMouseUp);
|
||||
|
||||
this._boundOnNewSelection = this._onNewSelection.bind(this);
|
||||
this._inspector.selection.on("new-node-front", this._boundOnNewSelection);
|
||||
this._onNewSelection();
|
||||
|
@ -156,7 +164,49 @@ MarkupView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
isDragging: false,
|
||||
|
||||
_onMouseMove: function(event) {
|
||||
if (this.isDragging) {
|
||||
event.preventDefault();
|
||||
this._dragStartEl = event.target;
|
||||
|
||||
let docEl = this.doc.documentElement;
|
||||
|
||||
if (this._scrollInterval) {
|
||||
this.win.clearInterval(this._scrollInterval);
|
||||
}
|
||||
|
||||
// Auto-scroll when the mouse approaches top/bottom edge
|
||||
let distanceFromBottom = docEl.clientHeight - event.pageY + this.win.scrollY,
|
||||
distanceFromTop = event.pageY - this.win.scrollY;
|
||||
|
||||
if (distanceFromBottom <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) {
|
||||
// Map our distance from 0-50 to 5-15 range so the speed is kept
|
||||
// in a range not too fast, not too slow
|
||||
let speed = map(distanceFromBottom, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE,
|
||||
DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED);
|
||||
// Here, we use minus because the value of speed - 15 is always negative
|
||||
// and it makes the speed relative to the distance between mouse and edge
|
||||
// the closer to the edge, the faster
|
||||
this._scrollInterval = this.win.setInterval(() => {
|
||||
docEl.scrollTop -= speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
if (distanceFromTop <= DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE) {
|
||||
// refer to bottom edge's comments for more info
|
||||
let speed = map(distanceFromTop, 0, DRAG_DROP_AUTOSCROLL_EDGE_DISTANCE,
|
||||
DRAG_DROP_MIN_AUTOSCROLL_SPEED, DRAG_DROP_MAX_AUTOSCROLL_SPEED);
|
||||
|
||||
this._scrollInterval = this.win.setInterval(() => {
|
||||
docEl.scrollTop += speed - DRAG_DROP_MAX_AUTOSCROLL_SPEED;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
return;
|
||||
};
|
||||
|
||||
let target = event.target;
|
||||
|
||||
// Search target for a markupContainer reference, if not found, walk up
|
||||
|
@ -198,6 +248,18 @@ MarkupView.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
_onMouseUp: function() {
|
||||
if (this._lastDropTarget) {
|
||||
this.indicateDropTarget(null);
|
||||
}
|
||||
if (this._lastDragTarget) {
|
||||
this.indicateDragTarget(null);
|
||||
}
|
||||
if (this._scrollInterval) {
|
||||
this.win.clearInterval(this._scrollInterval);
|
||||
}
|
||||
},
|
||||
|
||||
_hoveredNode: null,
|
||||
|
||||
/**
|
||||
|
@ -218,6 +280,11 @@ MarkupView.prototype = {
|
|||
},
|
||||
|
||||
_onMouseLeave: function() {
|
||||
if (this._scrollInterval) {
|
||||
this.win.clearInterval(this._scrollInterval);
|
||||
}
|
||||
if (this.isDragging) return;
|
||||
|
||||
this._hideBoxModel(true);
|
||||
if (this._hoveredNode) {
|
||||
this.getContainer(this._hoveredNode).hovered = false;
|
||||
|
@ -790,6 +857,10 @@ MarkupView.prototype = {
|
|||
*/
|
||||
_expandContainer: function(aContainer) {
|
||||
return this._updateChildren(aContainer, {expand: true}).then(() => {
|
||||
if (this._destroyer) {
|
||||
console.warn("Could not expand the node, the markup-view was destroyed");
|
||||
return;
|
||||
}
|
||||
aContainer.expanded = true;
|
||||
});
|
||||
},
|
||||
|
@ -1361,6 +1432,12 @@ MarkupView.prototype = {
|
|||
this.tooltip.destroy();
|
||||
this.tooltip = null;
|
||||
|
||||
this.win = null;
|
||||
this.doc = null;
|
||||
|
||||
this._lastDropTarget = null;
|
||||
this._lastDragTarget = null;
|
||||
|
||||
return this._destroyer;
|
||||
},
|
||||
|
||||
|
@ -1448,6 +1525,80 @@ MarkupView.prototype = {
|
|||
this._updatePreview();
|
||||
this._previewBar.classList.remove("hide");
|
||||
}, 1000);
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an element as it's only argument and marks the element
|
||||
* as the drop target
|
||||
*/
|
||||
indicateDropTarget: function(el) {
|
||||
if (this._lastDropTarget) {
|
||||
this._lastDropTarget.classList.remove("drop-target");
|
||||
}
|
||||
|
||||
if (!el) return;
|
||||
|
||||
let target = el.classList.contains("tag-line") ?
|
||||
el : el.querySelector(".tag-line") || el.closest(".tag-line");
|
||||
if (!target) return;
|
||||
|
||||
target.classList.add("drop-target");
|
||||
this._lastDropTarget = target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Takes an element to mark it as indicator of dragging target's initial place
|
||||
*/
|
||||
indicateDragTarget: function(el) {
|
||||
if (this._lastDragTarget) {
|
||||
this._lastDragTarget.classList.remove("drag-target");
|
||||
}
|
||||
|
||||
if (!el) return;
|
||||
|
||||
let target = el.classList.contains("tag-line") ?
|
||||
el : el.querySelector(".tag-line") || el.closest(".tag-line");
|
||||
|
||||
if (!target) return;
|
||||
|
||||
target.classList.add("drag-target");
|
||||
this._lastDragTarget = target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to get the nodes required to modify the markup after dragging the element (parent/nextSibling)
|
||||
*/
|
||||
get dropTargetNodes() {
|
||||
let target = this._lastDropTarget;
|
||||
|
||||
if (!target) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let parent, nextSibling;
|
||||
|
||||
if (this._lastDropTarget.previousElementSibling &&
|
||||
this._lastDropTarget.previousElementSibling.nodeName.toLowerCase() === "ul") {
|
||||
parent = target.parentNode.container.node;
|
||||
nextSibling = null;
|
||||
} else {
|
||||
parent = target.parentNode.container.node.parentNode();
|
||||
nextSibling = target.parentNode.container.node;
|
||||
}
|
||||
|
||||
if (nextSibling && nextSibling.isBeforePseudoElement) {
|
||||
nextSibling = target.parentNode.parentNode.children[1].container.node;
|
||||
}
|
||||
if (nextSibling && nextSibling.isAfterPseudoElement) {
|
||||
parent = target.parentNode.container.node.parentNode();
|
||||
nextSibling = null;
|
||||
}
|
||||
|
||||
if (parent.nodeType !== Ci.nsIDOMNode.ELEMENT_NODE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {parent, nextSibling};
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1481,6 +1632,7 @@ MarkupContainer.prototype = {
|
|||
this.markup = markupView;
|
||||
this.node = node;
|
||||
this.undo = this.markup.undo;
|
||||
this.win = this.markup._frame.contentWindow;
|
||||
|
||||
// The template will fill the following properties
|
||||
this.elt = null;
|
||||
|
@ -1491,15 +1643,16 @@ MarkupContainer.prototype = {
|
|||
this.markup.template(templateID, this);
|
||||
this.elt.container = this;
|
||||
|
||||
// Binding event listeners
|
||||
this._onMouseDown = this._onMouseDown.bind(this);
|
||||
this.elt.addEventListener("mousedown", this._onMouseDown, false);
|
||||
|
||||
this._onToggle = this._onToggle.bind(this);
|
||||
this._onMouseUp = this._onMouseUp.bind(this);
|
||||
this._onMouseMove = this._onMouseMove.bind(this);
|
||||
|
||||
// Expanding/collapsing the node on dblclick of the whole tag-line element
|
||||
// Binding event listeners
|
||||
this.elt.addEventListener("mousedown", this._onMouseDown, false);
|
||||
this.markup.doc.body.addEventListener("mouseup", this._onMouseUp, true);
|
||||
this.markup.doc.body.addEventListener("mousemove", this._onMouseMove, true);
|
||||
this.elt.addEventListener("dblclick", this._onToggle, false);
|
||||
|
||||
if (this.expander) {
|
||||
this.expander.addEventListener("click", this._onToggle, false);
|
||||
}
|
||||
|
@ -1618,24 +1771,109 @@ MarkupContainer.prototype = {
|
|||
return this.elt.parentNode ? this.elt.parentNode.container : null;
|
||||
},
|
||||
|
||||
_isMouseDown: false,
|
||||
_isDragging: false,
|
||||
_dragStartY: 0,
|
||||
|
||||
set isDragging(isDragging) {
|
||||
this._isDragging = isDragging;
|
||||
this.markup.isDragging = isDragging;
|
||||
|
||||
if (isDragging) {
|
||||
this.elt.classList.add("dragging");
|
||||
this.markup.doc.body.classList.add("dragging");
|
||||
} else {
|
||||
this.elt.classList.remove("dragging");
|
||||
this.markup.doc.body.classList.remove("dragging");
|
||||
}
|
||||
},
|
||||
|
||||
get isDragging() {
|
||||
return this._isDragging;
|
||||
},
|
||||
|
||||
_onMouseDown: function(event) {
|
||||
let target = event.target;
|
||||
|
||||
// Target may be a resource link (generated by the output-parser)
|
||||
// The "show more nodes" button (already has its onclick).
|
||||
if (target.nodeName === "button") {
|
||||
return;
|
||||
}
|
||||
|
||||
// output-parser generated links handling.
|
||||
if (target.nodeName === "a") {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
let browserWin = this.markup._inspector.target
|
||||
.tab.ownerDocument.defaultView;
|
||||
browserWin.openUILinkIn(target.href, "tab");
|
||||
return;
|
||||
}
|
||||
// Or it may be the "show more nodes" button (which already has its onclick)
|
||||
// Else, it's the container itself
|
||||
else if (target.nodeName !== "button") {
|
||||
this.hovered = false;
|
||||
this.markup.navigate(this);
|
||||
event.stopPropagation();
|
||||
|
||||
// target is the MarkupContainer itself.
|
||||
this._isMouseDown = true;
|
||||
this.hovered = false;
|
||||
this.markup.navigate(this);
|
||||
event.stopPropagation();
|
||||
|
||||
// Start dragging the container after a delay.
|
||||
this.markup._dragStartEl = target;
|
||||
this.win.setTimeout(() => {
|
||||
// Make sure the mouse is still down and on target.
|
||||
if (!this._isMouseDown || this.markup._dragStartEl !== target ||
|
||||
this.node.isPseudoElement || this.node.isAnonymous ||
|
||||
!this.win.getSelection().isCollapsed) {
|
||||
return;
|
||||
}
|
||||
this.isDragging = true;
|
||||
|
||||
this._dragStartY = event.pageY;
|
||||
this.markup.indicateDropTarget(this.elt);
|
||||
|
||||
// If this is the last child, use the closing <div.tag-line> of parent as indicator
|
||||
this.markup.indicateDragTarget(this.elt.nextElementSibling ||
|
||||
this.markup.getContainer(this.node.parentNode()).closeTagLine);
|
||||
}, GRAB_DELAY);
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouse up, stop dragging.
|
||||
*/
|
||||
_onMouseUp: function(event) {
|
||||
this._isMouseDown = false;
|
||||
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragging = false;
|
||||
this.elt.style.removeProperty("top");
|
||||
|
||||
let dropTargetNodes = this.markup.dropTargetNodes;
|
||||
|
||||
if(!dropTargetNodes) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.markup.walker.insertBefore(this.node, dropTargetNodes.parent,
|
||||
dropTargetNodes.nextSibling);
|
||||
},
|
||||
|
||||
/**
|
||||
* On mouse move, move the dragged element if any and indicate the drop target.
|
||||
*/
|
||||
_onMouseMove: function(event) {
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
|
||||
let diff = event.pageY - this._dragStartY;
|
||||
this.elt.style.top = diff + "px";
|
||||
|
||||
let el = this.markup.doc.elementFromPoint(event.pageX - this.win.scrollX,
|
||||
event.pageY - this.win.scrollY);
|
||||
|
||||
this.markup.indicateDropTarget(el);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -1644,7 +1882,7 @@ MarkupContainer.prototype = {
|
|||
*/
|
||||
flashMutation: function() {
|
||||
if (!this.selected) {
|
||||
let contentWin = this.markup._frame.contentWindow;
|
||||
let contentWin = this.win;
|
||||
this.flashed = true;
|
||||
if (this._flashMutationTimer) {
|
||||
contentWin.clearTimeout(this._flashMutationTimer);
|
||||
|
@ -1777,6 +2015,10 @@ MarkupContainer.prototype = {
|
|||
// Remove event listeners
|
||||
this.elt.removeEventListener("mousedown", this._onMouseDown, false);
|
||||
this.elt.removeEventListener("dblclick", this._onToggle, false);
|
||||
this.markup.doc.body.removeEventListener("mouseup", this._onMouseUp, true);
|
||||
this.markup.doc.body.removeEventListener("mousemove", this._onMouseMove, true);
|
||||
|
||||
this.win = null;
|
||||
|
||||
if (this.expander) {
|
||||
this.expander.removeEventListener("click", this._onToggle, false);
|
||||
|
@ -2515,6 +2757,17 @@ function parseAttributeValues(attr, doc) {
|
|||
return attributes.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a number from one range to another.
|
||||
*/
|
||||
function map(value, oldMin, oldMax, newMin, newMax) {
|
||||
let ratio = oldMax - oldMin;
|
||||
if (ratio == 0) {
|
||||
return value;
|
||||
}
|
||||
return newMin + (newMax - newMin) * ((value - oldMin) / ratio);
|
||||
}
|
||||
|
||||
loader.lazyGetter(MarkupView.prototype, "strings", () => Services.strings.createBundle(
|
||||
"chrome://browser/locale/devtools/inspector.properties"
|
||||
));
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
subsuite = devtools
|
||||
support-files =
|
||||
doc_markup_anonymous.html
|
||||
doc_markup_dragdrop.html
|
||||
doc_markup_dragdrop_autoscroll.html
|
||||
doc_markup_edit.html
|
||||
doc_markup_events.html
|
||||
doc_markup_events_jquery.html
|
||||
|
@ -37,6 +39,11 @@ skip-if = e10s # scratchpad.xul is not loading in e10s window
|
|||
[browser_markupview_anonymous_04.js]
|
||||
[browser_markupview_copy_image_data.js]
|
||||
[browser_markupview_css_completion_style_attribute.js]
|
||||
[browser_markupview_dragdrop_autoscroll.js]
|
||||
[browser_markupview_dragdrop_invalidNodes.js]
|
||||
[browser_markupview_dragdrop_isDragging.js]
|
||||
[browser_markupview_dragdrop_reorder.js]
|
||||
[browser_markupview_dragdrop_textSelection.js]
|
||||
[browser_markupview_events.js]
|
||||
skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
|
||||
[browser_markupview_events-overflow.js]
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/* 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: Dragging nodes near top/bottom edges of inspector
|
||||
// should auto-scroll
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop_autoscroll.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
let markup = inspector.markup;
|
||||
|
||||
let container = yield getContainerForSelector("#first", inspector);
|
||||
let rect = container.elt.getBoundingClientRect();
|
||||
|
||||
info("Simulating mouseDown on #first");
|
||||
container._onMouseDown({
|
||||
target: container.tagLine,
|
||||
pageX: 10,
|
||||
pageY: rect.top,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
|
||||
let clientHeight = markup.doc.documentElement.clientHeight;
|
||||
info("Simulating mouseMove on #first with pageY: " + clientHeight);
|
||||
|
||||
let ev = {
|
||||
target: container.tagLine,
|
||||
pageX: 10,
|
||||
pageY: clientHeight,
|
||||
preventDefault: function() {}
|
||||
};
|
||||
|
||||
info("Listening on scroll event");
|
||||
let scroll = onScroll(markup.win);
|
||||
|
||||
markup._onMouseMove(ev);
|
||||
|
||||
yield scroll;
|
||||
|
||||
container._onMouseUp(ev);
|
||||
markup._onMouseUp(ev);
|
||||
|
||||
ok("Scroll event fired");
|
||||
});
|
||||
|
||||
function onScroll(win) {
|
||||
return new Promise((resolve, reject) => {
|
||||
win.onscroll = function(e) {
|
||||
resolve(e);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
/* 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: pseudo-elements and anonymous nodes should not be draggable
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
|
||||
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Expanding #test");
|
||||
let parentFront = yield getNodeFront("#test", inspector);
|
||||
yield inspector.markup.expandNode(parentFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
|
||||
let beforePseudo = parentContainer.elt.children[1].firstChild.container;
|
||||
|
||||
parentContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Simulating mouseDown on #test::before");
|
||||
beforePseudo._onMouseDown({
|
||||
target: beforePseudo.tagLine,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
info("Waiting " + (GRAB_DELAY + 1) + "ms")
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
is(beforePseudo.isDragging, false, "[pseudo-element] isDragging is false after GRAB_DELAY has passed");
|
||||
|
||||
let inputFront = yield getNodeFront("#anonymousParent", inspector);
|
||||
|
||||
yield inspector.markup.expandNode(inputFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
let inputContainer = yield getContainerForNodeFront(inputFront, inspector);
|
||||
let anonymousDiv = inputContainer.elt.children[1].firstChild.container;
|
||||
|
||||
inputContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Simulating mouseDown on input#anonymousParent div");
|
||||
anonymousDiv._onMouseDown({
|
||||
target: anonymousDiv.tagLine,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
info("Waiting " + (GRAB_DELAY + 1) + "ms")
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
is(anonymousDiv.isDragging, false, "[anonymous element] isDragging is false after GRAB_DELAY has passed");
|
||||
});
|
|
@ -0,0 +1,41 @@
|
|||
/* 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 drag mode's delay, it shouldn't enable dragging before
|
||||
// GRAB_DELAY = 400 has passed
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
let el = yield getContainerForSelector("#test", inspector);
|
||||
let rect = el.tagLine.getBoundingClientRect();
|
||||
|
||||
info("Simulating mouseDown on #test");
|
||||
el._onMouseDown({
|
||||
target: el.tagLine,
|
||||
pageX: rect.x,
|
||||
pageY: rect.y,
|
||||
stopPropagation: function() {}
|
||||
});
|
||||
|
||||
is(el.isDragging, false, "isDragging should not be set to true immedietly");
|
||||
|
||||
info("Waiting " + (GRAB_DELAY + 1) + "ms");
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
ok(el.isDragging, "isDragging true after GRAB_DELAY has passed");
|
||||
|
||||
info("Simulating mouseUp on #test");
|
||||
el._onMouseUp({
|
||||
target: el.tagLine,
|
||||
pageX: rect.x,
|
||||
pageY: rect.y
|
||||
});
|
||||
|
||||
is(el.isDragging, false, "isDragging false after mouseUp");
|
||||
});
|
|
@ -0,0 +1,109 @@
|
|||
/* 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 different kinds of drag and drop node re-ordering
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Expanding #test");
|
||||
let parentFront = yield getNodeFront("#test", inspector);
|
||||
let parent = yield getNode("#test");
|
||||
let parentContainer = yield getContainerForNodeFront(parentFront, inspector);
|
||||
|
||||
yield inspector.markup.expandNode(parentFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
parentContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Testing switching elements inside their parent");
|
||||
yield moveElementDown("#firstChild", "#middleChild", inspector);
|
||||
|
||||
is(parent.children[0].id, "middleChild", "#firstChild is now the second child of #test");
|
||||
is(parent.children[1].id, "firstChild", "#middleChild is now the first child of #test");
|
||||
|
||||
info("Testing switching elements with a last child");
|
||||
yield moveElementDown("#firstChild", "#lastChild", inspector);
|
||||
|
||||
is(parent.children[1].id, "lastChild", "#lastChild is now the second child of #test");
|
||||
is(parent.children[2].id, "firstChild", "#firstChild is now the last child of #test");
|
||||
|
||||
info("Testing appending element to a parent");
|
||||
yield moveElementDown("#before", "#test", inspector);
|
||||
|
||||
is(parent.children.length, 4, "New element appended to #test");
|
||||
is(parent.children[0].id, "before", "New element is appended at the right place (currently first child)");
|
||||
|
||||
info("Testing moving element to after it's parent");
|
||||
yield moveElementDown("#firstChild", "#test", inspector);
|
||||
|
||||
is(parent.children.length, 3, "#firstChild is no longer #test's child");
|
||||
is(parent.nextElementSibling.id, "firstChild", "#firstChild is now #test's nextElementSibling");
|
||||
});
|
||||
|
||||
function* dragContainer(selector, targetOffset, inspector) {
|
||||
info("Dragging the markup-container for node " + selector);
|
||||
|
||||
let container = yield getContainerForSelector(selector, inspector);
|
||||
|
||||
let updated = inspector.once("inspector-updated");
|
||||
|
||||
let rect = {
|
||||
x: container.tagLine.offsetLeft,
|
||||
y: container.tagLine.offsetTop
|
||||
};
|
||||
|
||||
info("Simulating mouseDown on " + selector);
|
||||
container._onMouseDown({
|
||||
target: container.tagLine,
|
||||
pageX: rect.x,
|
||||
pageY: rect.y,
|
||||
stopPropagation: function() {}
|
||||
});
|
||||
|
||||
let targetX = rect.x + targetOffset.x,
|
||||
targetY = rect.y + targetOffset.y;
|
||||
|
||||
setTimeout(() => {
|
||||
info("Simulating mouseMove on " + selector +
|
||||
" with pageX: " + targetX + " pageY: " + targetY);
|
||||
container._onMouseMove({
|
||||
target: container.tagLine,
|
||||
pageX: targetX,
|
||||
pageY: targetY
|
||||
});
|
||||
|
||||
info("Simulating mouseUp on " + selector +
|
||||
" with pageX: " + targetX + " pageY: " + targetY);
|
||||
container._onMouseUp({
|
||||
target: container.tagLine,
|
||||
pageX: targetX,
|
||||
pageY: targetY
|
||||
});
|
||||
|
||||
container.markup._onMouseUp();
|
||||
}, GRAB_DELAY+1);
|
||||
|
||||
return updated;
|
||||
};
|
||||
|
||||
function* moveElementDown(selector, next, inspector) {
|
||||
let onMutated = inspector.once("markupmutation");
|
||||
let uiUpdate = inspector.once("inspector-updated");
|
||||
|
||||
let el = yield getContainerForSelector(next, inspector);
|
||||
let height = el.tagLine.getBoundingClientRect().height;
|
||||
|
||||
info("Switching " + selector + ' with ' + next);
|
||||
|
||||
yield dragContainer(selector, {x: 0, y: Math.round(height) + 2}, inspector);
|
||||
|
||||
yield onMutated;
|
||||
yield uiUpdate;
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
/* 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: Nodes should not be draggable if there is a text selected
|
||||
// (trying to move selected text around shouldn't trigger node drag and drop)
|
||||
|
||||
const TEST_URL = TEST_URL_ROOT + "doc_markup_dragdrop.html";
|
||||
const GRAB_DELAY = 400;
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
let markup = inspector.markup;
|
||||
|
||||
info("Expanding span#before");
|
||||
let spanFront = yield getNodeFront("#before", inspector);
|
||||
let spanContainer = yield getContainerForNodeFront(spanFront, inspector);
|
||||
let span = yield getNode("#before");
|
||||
|
||||
yield inspector.markup.expandNode(spanFront);
|
||||
yield waitForMultipleChildrenUpdates(inspector);
|
||||
|
||||
spanContainer.elt.scrollIntoView(true);
|
||||
|
||||
info("Selecting #before's text content");
|
||||
|
||||
let textContent = spanContainer.elt.children[1].firstChild.container;
|
||||
|
||||
let selectRange = markup.doc.createRange();
|
||||
selectRange.selectNode(textContent.editor.elt.querySelector('[tabindex]'));
|
||||
markup.doc.getSelection().addRange(selectRange);
|
||||
|
||||
info("Simulating mouseDown on #before");
|
||||
|
||||
spanContainer._onMouseDown({
|
||||
pageX: 0,
|
||||
pageY: 0,
|
||||
target: spanContainer.tagLine,
|
||||
stopPropagation: function() {},
|
||||
preventDefault: function() {}
|
||||
});
|
||||
|
||||
yield wait(GRAB_DELAY + 1);
|
||||
|
||||
is(spanContainer.isDragging, false, "isDragging should be false if there is a text selected");
|
||||
});
|
|
@ -31,15 +31,3 @@ add_task(function*() {
|
|||
|
||||
yield inspector.once("inspector-updated");
|
||||
});
|
||||
|
||||
// The expand all operation of the markup-view calls itself recursively and
|
||||
// there's not one event we can wait for to know when it's done
|
||||
function* waitForMultipleChildrenUpdates(inspector) {
|
||||
// As long as child updates are queued up while we wait for an update already
|
||||
// wait again
|
||||
if (inspector.markup._queuedChildUpdates &&
|
||||
inspector.markup._queuedChildUpdates.size) {
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
return yield waitForMultipleChildrenUpdates(inspector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,15 +33,3 @@ add_task(function*() {
|
|||
"Container for node " + nodeFront.tagName + " is expanded");
|
||||
}
|
||||
});
|
||||
|
||||
// The expand all operation of the markup-view calls itself recursively and
|
||||
// there's not one event we can wait for to know when it's done
|
||||
function* waitForMultipleChildrenUpdates(inspector) {
|
||||
// As long as child updates are queued up while we wait for an update already
|
||||
// wait again
|
||||
if (inspector.markup._queuedChildUpdates &&
|
||||
inspector.markup._queuedChildUpdates.size) {
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
return yield waitForMultipleChildrenUpdates(inspector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=858038
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 858038</title>
|
||||
<style>
|
||||
#test::before {
|
||||
content: 'This should not be draggable';
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858038">Mozilla Bug 858038</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<input id="anonymousParent" />
|
||||
|
||||
<span id="before">Before</span>
|
||||
<pre id="test">
|
||||
<span id="firstChild">First</span>
|
||||
<span id="middleChild">Middle</span>
|
||||
<span id="lastChild">Last</span>
|
||||
</pre>
|
||||
<span id="after">After</span>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,51 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=858038
|
||||
-->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for Bug 858038 - Autoscroll</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="first"></div>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858038">Mozilla Bug 858038</a>
|
||||
<p id="display">Test</p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Make sure the markup-view has enough nodes shown by default that it has a scrollbar -->
|
||||
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</body>
|
||||
</html>
|
|
@ -561,3 +561,16 @@ function checkFocusedAttribute(attrName, editMode) {
|
|||
editMode ? "input": "span",
|
||||
editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode");
|
||||
}
|
||||
|
||||
// The expand all operation of the markup-view calls itself recursively and
|
||||
// there's not one event we can wait for to know when it's done
|
||||
// so use this helper function to wait until all recursive children updates are done.
|
||||
function* waitForMultipleChildrenUpdates(inspector) {
|
||||
// As long as child updates are queued up while we wait for an update already
|
||||
// wait again
|
||||
if (inspector.markup._queuedChildUpdates &&
|
||||
inspector.markup._queuedChildUpdates.size) {
|
||||
yield waitForChildrenUpdated(inspector);
|
||||
return yield waitForMultipleChildrenUpdates(inspector);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,6 @@ function test() {
|
|||
function testPrefs() {
|
||||
let { Prefs } = aMonitor.panelWin;
|
||||
|
||||
is(Prefs._root, "devtools.netmonitor",
|
||||
"The preferences object should have a correct root path.");
|
||||
|
||||
is(Prefs.networkDetailsWidth,
|
||||
Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
|
||||
"Getting a pref should work correctly.");
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
|
||||
/**
|
||||
* Utility functions for managing recording models and their internal data,
|
||||
* such as filtering profile samples or offsetting timestamps.
|
||||
|
@ -130,3 +132,50 @@ exports.RecordingUtils.getSamplesFromAllocations = function(allocations) {
|
|||
gSamplesFromAllocationCache.set(allocations, samples);
|
||||
return samples;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current timeline blueprint without the hidden markers.
|
||||
*
|
||||
* @param blueprint
|
||||
* The default timeline blueprint.
|
||||
* @param array hiddenMarkers
|
||||
* A list of hidden markers' names.
|
||||
* @return object
|
||||
* The filtered timeline blueprint.
|
||||
*/
|
||||
exports.RecordingUtils.getFilteredBlueprint = function({ blueprint, hiddenMarkers }) {
|
||||
let filteredBlueprint = Cu.cloneInto(blueprint, {});
|
||||
let maybeRemovedGroups = new Set();
|
||||
let removedGroups = new Set();
|
||||
|
||||
// 1. Remove hidden markers from the blueprint.
|
||||
|
||||
for (let hiddenMarkerName of hiddenMarkers) {
|
||||
maybeRemovedGroups.add(filteredBlueprint[hiddenMarkerName].group);
|
||||
delete filteredBlueprint[hiddenMarkerName];
|
||||
}
|
||||
|
||||
// 2. Get a list of all the groups that will be removed.
|
||||
|
||||
for (let maybeRemovedGroup of maybeRemovedGroups) {
|
||||
let markerNames = Object.keys(filteredBlueprint);
|
||||
let isGroupRemoved = markerNames.every(e => filteredBlueprint[e].group != maybeRemovedGroup);
|
||||
if (isGroupRemoved) {
|
||||
removedGroups.add(maybeRemovedGroup);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Offset groups so that their indices are consecutive.
|
||||
|
||||
for (let removedGroup of removedGroups) {
|
||||
let markerNames = Object.keys(filteredBlueprint);
|
||||
for (let markerName of markerNames) {
|
||||
let markerDetails = filteredBlueprint[markerName];
|
||||
if (markerDetails.group > removedGroup) {
|
||||
markerDetails.group--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredBlueprint;
|
||||
};
|
||||
|
|
|
@ -57,10 +57,12 @@ const BRANCH_NAME = "devtools.performance.ui.";
|
|||
|
||||
// Events emitted by various objects in the panel.
|
||||
const EVENTS = {
|
||||
// Fired by the OptionsView when a preference changes.
|
||||
// Fired by the PerformanceController and OptionsView when a pref changes.
|
||||
PREF_CHANGED: "Performance:PrefChanged",
|
||||
|
||||
// Emitted by the PerformanceView when the state (display mode) changes.
|
||||
// Emitted by the PerformanceView when the state (display mode) changes,
|
||||
// for example when switching between "empty", "recording" or "recorded".
|
||||
// This causes certain panels to be hidden or visible.
|
||||
UI_STATE_CHANGED: "Performance:UI:StateChanged",
|
||||
|
||||
// Emitted by the PerformanceView on clear button click
|
||||
|
@ -176,6 +178,16 @@ let PerformanceController = {
|
|||
this._onRecordingSelectFromView = this._onRecordingSelectFromView.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
|
||||
// All boolean prefs should be handled via the OptionsView in the
|
||||
// ToolbarView, so that they may be accessible via the "gear" menu.
|
||||
// Every other pref should be registered here.
|
||||
this._nonBooleanPrefs = new ViewHelpers.Prefs("devtools.performance", {
|
||||
"hidden-markers": ["Json", "timeline.hidden-markers"]
|
||||
});
|
||||
|
||||
this._nonBooleanPrefs.registerObserver();
|
||||
this._nonBooleanPrefs.on("pref-changed", this._onPrefChanged);
|
||||
|
||||
ToolbarView.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
|
@ -195,6 +207,9 @@ let PerformanceController = {
|
|||
* Remove events handled by the PerformanceController
|
||||
*/
|
||||
destroy: function() {
|
||||
this._nonBooleanPrefs.unregisterObserver();
|
||||
this._nonBooleanPrefs.off("pref-changed", this._onPrefChanged);
|
||||
|
||||
ToolbarView.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
|
||||
PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
|
||||
|
@ -211,11 +226,32 @@ let PerformanceController = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Get a preference setting from `prefName` via the underlying
|
||||
* Get a boolean preference setting from `prefName` via the underlying
|
||||
* OptionsView in the ToolbarView.
|
||||
*
|
||||
* @param string prefName
|
||||
* @return boolean
|
||||
*/
|
||||
getOption: function (prefName) {
|
||||
return ToolbarView.optionsView.getPref(prefName);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get a preference setting from `prefName`.
|
||||
* @param string prefName
|
||||
* @return object
|
||||
*/
|
||||
getPref: function (prefName) {
|
||||
return ToolbarView.optionsView.getPref(prefName);
|
||||
return this._nonBooleanPrefs[prefName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a preference setting from `prefName`.
|
||||
* @param string prefName
|
||||
* @param object prefValue
|
||||
*/
|
||||
setPref: function (prefName, prefValue) {
|
||||
this._nonBooleanPrefs[prefName] = prefValue;
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -223,9 +259,9 @@ let PerformanceController = {
|
|||
* when the front has started to record.
|
||||
*/
|
||||
startRecording: Task.async(function *() {
|
||||
let withMemory = this.getPref("enable-memory");
|
||||
let withTicks = this.getPref("enable-framerate");
|
||||
let withAllocations = this.getPref("enable-memory");
|
||||
let withMemory = this.getOption("enable-memory");
|
||||
let withTicks = this.getOption("enable-framerate");
|
||||
let withAllocations = this.getOption("enable-memory");
|
||||
|
||||
let recording = this._createRecording({ withMemory, withTicks, withAllocations });
|
||||
|
||||
|
@ -241,7 +277,7 @@ let PerformanceController = {
|
|||
* when the front has stopped recording.
|
||||
*/
|
||||
stopRecording: Task.async(function *() {
|
||||
let recording = this._getLatestRecording();
|
||||
let recording = this.getLatestRecording();
|
||||
|
||||
this.emit(EVENTS.RECORDING_WILL_STOP, recording);
|
||||
yield recording.stopRecording();
|
||||
|
@ -267,7 +303,7 @@ let PerformanceController = {
|
|||
* Emits `EVENTS.RECORDINGS_CLEARED` when complete so other components can clean up.
|
||||
*/
|
||||
clearRecordings: Task.async(function* () {
|
||||
let latest = this._getLatestRecording();
|
||||
let latest = this.getLatestRecording();
|
||||
|
||||
if (latest && latest.isRecording()) {
|
||||
yield this.stopRecording();
|
||||
|
@ -335,13 +371,23 @@ let PerformanceController = {
|
|||
* Get most recently added recording that was triggered manually (via UI).
|
||||
* @return RecordingModel
|
||||
*/
|
||||
_getLatestRecording: function () {
|
||||
getLatestRecording: function () {
|
||||
for (let i = this._recordings.length - 1; i >= 0; i--) {
|
||||
return this._recordings[i];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the current timeline blueprint without the hidden markers.
|
||||
* @return object
|
||||
*/
|
||||
getTimelineBlueprint: function() {
|
||||
let blueprint = TIMELINE_BLUEPRINT;
|
||||
let hiddenMarkers = this.getPref("hidden-markers");
|
||||
return RecordingUtils.getFilteredBlueprint({ blueprint, hiddenMarkers });
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired whenever the PerformanceFront emits markers, memory or ticks.
|
||||
*/
|
||||
|
@ -362,8 +408,8 @@ let PerformanceController = {
|
|||
* Fired when the ToolbarView fires a PREF_CHANGED event.
|
||||
* with the value.
|
||||
*/
|
||||
_onPrefChanged: function (_, prefName, value) {
|
||||
this.emit(EVENTS.PREF_CHANGED, prefName, value);
|
||||
_onPrefChanged: function (_, prefName, prefValue) {
|
||||
this.emit(EVENTS.PREF_CHANGED, prefName, prefValue);
|
||||
},
|
||||
|
||||
toString: () => "[object PerformanceController]"
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<script type="application/javascript" src="performance/views/recordings.js"/>
|
||||
|
||||
<popupset id="performance-options-popupset">
|
||||
<menupopup id="performance-filter-menupopup"/>
|
||||
<menupopup id="performance-options-menupopup">
|
||||
<menuitem id="option-show-platform-data"
|
||||
type="checkbox"
|
||||
|
@ -85,6 +86,12 @@
|
|||
|
||||
<vbox id="performance-pane" flex="1">
|
||||
<toolbar id="performance-toolbar" class="devtools-toolbar">
|
||||
<hbox id="performance-toolbar-control-other" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="filter-button"
|
||||
popup="performance-filter-menupopup"
|
||||
class="devtools-toolbarbutton"
|
||||
tooltiptext="&profilerUI.options.filter.tooltiptext;"/>
|
||||
</hbox>
|
||||
<hbox id="performance-toolbar-controls-detail-views" class="devtools-toolbarbutton-group">
|
||||
<toolbarbutton id="select-waterfall-view"
|
||||
class="devtools-toolbarbutton devtools-button"
|
||||
|
@ -112,7 +119,7 @@
|
|||
<toolbarbutton id="performance-options-button"
|
||||
class="devtools-toolbarbutton devtools-option-toolbarbutton"
|
||||
popup="performance-options-menupopup"
|
||||
tooltiptext="&profilerUI.options.tooltiptext;"/>
|
||||
tooltiptext="&profilerUI.options.gear.tooltiptext;"/>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
|
|
|
@ -97,3 +97,4 @@ support-files =
|
|||
[browser_profiler_tree-view-06.js]
|
||||
[browser_profiler_tree-view-07.js]
|
||||
[browser_timeline_blueprint.js]
|
||||
[browser_timeline_filters.js]
|
||||
|
|
|
@ -5,28 +5,29 @@
|
|||
* Tests markers filtering mechanism.
|
||||
*/
|
||||
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initTimelinePanel(SIMPLE_URL);
|
||||
let { $, $$, TimelineController, TimelineView } = panel.panelWin;
|
||||
function spawnTest () {
|
||||
let { panel } = yield initPerformance(SIMPLE_URL);
|
||||
let { $, $$, PerformanceController, OverviewView, WaterfallView } = panel.panelWin;
|
||||
let { MARKERS_GRAPH_ROW_HEIGHT } = panel.panelWin;
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
yield startRecording(panel);
|
||||
ok(true, "Recording has started.");
|
||||
|
||||
yield waitUntil(() => {
|
||||
// Wait until we get 3 different markers.
|
||||
let markers = TimelineController.getMarkers();
|
||||
let markers = PerformanceController.getCurrentRecording().getMarkers();
|
||||
return markers.some(m => m.name == "Styles") &&
|
||||
markers.some(m => m.name == "Reflow") &&
|
||||
markers.some(m => m.name == "Paint");
|
||||
});
|
||||
|
||||
yield TimelineController.toggleRecording();
|
||||
yield stopRecording(panel);
|
||||
|
||||
let overview = TimelineView.markersOverview;
|
||||
let waterfall = TimelineView.waterfall;
|
||||
let overview = OverviewView.markersOverview;
|
||||
let waterfall = WaterfallView.waterfall;
|
||||
|
||||
// Select everything
|
||||
overview.setSelection({ start: 0, end: overview.width })
|
||||
OverviewView.setTimeInterval({ startTime: 0, endTime: Number.MAX_VALUE })
|
||||
|
||||
$("#filter-button").click();
|
||||
|
||||
|
@ -70,8 +71,7 @@ add_task(function*() {
|
|||
|
||||
yield waitUntil(() => !waterfall._outstandingMarkers.length);
|
||||
|
||||
// A row is 11px. See markers-overview.js
|
||||
is(overview.fixedHeight, heightBefore - 11, "Overview is smaller");
|
||||
is(overview.fixedHeight, heightBefore - MARKERS_GRAPH_ROW_HEIGHT, "Overview is smaller");
|
||||
ok(!$(".waterfall-marker-bar[type=Styles]"), "No 'Styles' marker (4)");
|
||||
ok(!$(".waterfall-marker-bar[type=Reflow]"), "No 'Reflow' marker (4)");
|
||||
ok(!$(".waterfall-marker-bar[type=Paint]"), "No 'Paint' marker (4)");
|
||||
|
@ -89,5 +89,6 @@ add_task(function*() {
|
|||
|
||||
is(overview.fixedHeight, originalHeight, "Overview restored");
|
||||
|
||||
$(".waterfall-marker-bar[type=Styles]");
|
||||
});
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
|
@ -5,13 +5,16 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Profiler test page</title>
|
||||
<title>Performance test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script type="text/javascript">
|
||||
var x = 1;
|
||||
function test() {
|
||||
var a = "Hello world!";
|
||||
document.body.style.borderTop = x + "px solid red";
|
||||
x = 1^x;
|
||||
document.body.innerHeight; // flush pending reflows
|
||||
}
|
||||
|
||||
// Prevent this script from being garbage collected.
|
||||
|
|
|
@ -59,10 +59,16 @@ let DetailsSubview = {
|
|||
|
||||
/**
|
||||
* An array of preferences under `devtools.performance.ui.` that the view should
|
||||
* rerender upon change.
|
||||
* rerender and callback `this._onRerenderPrefChanged` upon change.
|
||||
*/
|
||||
rerenderPrefs: [],
|
||||
|
||||
/**
|
||||
* An array of preferences under `devtools.performance.` that the view should
|
||||
* observe and callback `this._onObservedPrefChange` upon change.
|
||||
*/
|
||||
observedPrefs: [],
|
||||
|
||||
/**
|
||||
* Called when recording stops or is selected.
|
||||
*/
|
||||
|
@ -103,6 +109,10 @@ let DetailsSubview = {
|
|||
* Fired when a preference in `devtools.performance.ui.` is changed.
|
||||
*/
|
||||
_onPrefChanged: function (_, prefName) {
|
||||
if (~this.observedPrefs.indexOf(prefName) && this._onObservedPrefChange) {
|
||||
this._onObservedPrefChange(_, prefName);
|
||||
}
|
||||
|
||||
// All detail views require a recording to be complete, so do not
|
||||
// attempt to render if recording is in progress or does not exist.
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
|
@ -115,7 +125,7 @@ let DetailsSubview = {
|
|||
}
|
||||
|
||||
if (this._onRerenderPrefChanged) {
|
||||
this._onRerenderPrefChanged();
|
||||
this._onRerenderPrefChanged(_, prefName);
|
||||
}
|
||||
|
||||
if (DetailsView.isViewSelected(this) || this.canUpdateWhileHidden) {
|
||||
|
|
|
@ -64,8 +64,8 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
*/
|
||||
_prepareCallTree: function (profile, { startTime, endTime }, options) {
|
||||
let threadSamples = profile.threads[0].samples;
|
||||
let contentOnly = !PerformanceController.getPref("show-platform-data");
|
||||
let invertTree = PerformanceController.getPref("invert-call-tree");
|
||||
let contentOnly = !PerformanceController.getOption("show-platform-data");
|
||||
let invertTree = PerformanceController.getOption("invert-call-tree");
|
||||
|
||||
let threadNode = new ThreadNode(threadSamples,
|
||||
{ startTime, endTime, contentOnly, invertTree });
|
||||
|
@ -108,7 +108,7 @@ let JsCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
|
||||
// When platform data isn't shown, hide the cateogry labels, since they're
|
||||
// only available for C++ frames.
|
||||
let contentOnly = !PerformanceController.getPref("show-platform-data");
|
||||
let contentOnly = !PerformanceController.getOption("show-platform-data");
|
||||
root.toggleCategories(!contentOnly);
|
||||
},
|
||||
|
||||
|
|
|
@ -53,10 +53,10 @@ let JsFlameGraphView = Heritage.extend(DetailsSubview, {
|
|||
let samples = profile.threads[0].samples;
|
||||
|
||||
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
|
||||
invertStack: PerformanceController.getPref("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getPref("flatten-tree-recursion"),
|
||||
filterFrames: !PerformanceController.getPref("show-platform-data") && FrameNode.isContent,
|
||||
showIdleBlocks: PerformanceController.getPref("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
invertStack: PerformanceController.getOption("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
|
||||
filterFrames: !PerformanceController.getOption("show-platform-data") && FrameNode.isContent,
|
||||
showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
});
|
||||
|
||||
this.graph.setData({ data,
|
||||
|
|
|
@ -62,7 +62,7 @@ let MemoryCallTreeView = Heritage.extend(DetailsSubview, {
|
|||
*/
|
||||
_prepareCallTree: function (allocations, { startTime, endTime }, options) {
|
||||
let samples = RecordingUtils.getSamplesFromAllocations(allocations);
|
||||
let invertTree = PerformanceController.getPref("invert-call-tree");
|
||||
let invertTree = PerformanceController.getOption("invert-call-tree");
|
||||
|
||||
let threadNode = new ThreadNode(samples,
|
||||
{ startTime, endTime, invertTree });
|
||||
|
|
|
@ -52,9 +52,9 @@ let MemoryFlameGraphView = Heritage.extend(DetailsSubview, {
|
|||
|
||||
let samples = RecordingUtils.getSamplesFromAllocations(allocations);
|
||||
let data = FlameGraphUtils.createFlameGraphDataFromSamples(samples, {
|
||||
invertStack: PerformanceController.getPref("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getPref("flatten-tree-recursion"),
|
||||
showIdleBlocks: PerformanceController.getPref("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
invertStack: PerformanceController.getOption("invert-flame-graph"),
|
||||
flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
|
||||
showIdleBlocks: PerformanceController.getOption("show-idle-blocks") && L10N.getStr("table.idle")
|
||||
});
|
||||
|
||||
this.graph.setData({ data,
|
||||
|
|
|
@ -8,6 +8,14 @@
|
|||
*/
|
||||
let WaterfallView = Heritage.extend(DetailsSubview, {
|
||||
|
||||
observedPrefs: [
|
||||
"hidden-markers"
|
||||
],
|
||||
|
||||
rerenderPrefs: [
|
||||
"hidden-markers"
|
||||
],
|
||||
|
||||
rangeChangeDebounceTime: 10, // ms
|
||||
|
||||
/**
|
||||
|
@ -16,7 +24,7 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
initialize: function () {
|
||||
DetailsSubview.initialize.call(this);
|
||||
|
||||
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"), TIMELINE_BLUEPRINT);
|
||||
this.waterfall = new Waterfall($("#waterfall-breakdown"), $("#waterfall-view"));
|
||||
this.details = new MarkerDetails($("#waterfall-details"), $("#waterfall-view > splitter"));
|
||||
|
||||
this._onMarkerSelected = this._onMarkerSelected.bind(this);
|
||||
|
@ -26,6 +34,8 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
this.waterfall.on("unselected", this._onMarkerSelected);
|
||||
this.details.on("resize", this._onResize);
|
||||
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
this.waterfall.recalculateBounds();
|
||||
},
|
||||
|
||||
|
@ -79,5 +89,13 @@ let WaterfallView = Heritage.extend(DetailsSubview, {
|
|||
this.render();
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever an observed pref is changed.
|
||||
*/
|
||||
_onObservedPrefChange: function(_, prefName) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.waterfall.setBlueprint(blueprint);
|
||||
},
|
||||
|
||||
toString: () => "[object WaterfallView]"
|
||||
});
|
||||
|
|
|
@ -4,19 +4,41 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Details view containing profiler call tree and markers waterfall. Manages
|
||||
* subviews and toggles visibility between them.
|
||||
* Details view containing call trees, flamegraphs and markers waterfall.
|
||||
* Manages subviews and toggles visibility between them.
|
||||
*/
|
||||
let DetailsView = {
|
||||
/**
|
||||
* Name to node+object mapping of subviews.
|
||||
* Name to (node id, view object, actor requirements, pref killswitch)
|
||||
* mapping of subviews.
|
||||
*/
|
||||
components: {
|
||||
"waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
|
||||
"js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
|
||||
"js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
|
||||
"memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
|
||||
"memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
|
||||
"waterfall": {
|
||||
id: "waterfall-view",
|
||||
view: WaterfallView,
|
||||
requires: ["timeline"]
|
||||
},
|
||||
"js-calltree": {
|
||||
id: "js-calltree-view",
|
||||
view: JsCallTreeView
|
||||
},
|
||||
"js-flamegraph": {
|
||||
id: "js-flamegraph-view",
|
||||
view: JsFlameGraphView,
|
||||
requires: ["timeline"]
|
||||
},
|
||||
"memory-calltree": {
|
||||
id: "memory-calltree-view",
|
||||
view: MemoryCallTreeView,
|
||||
requires: ["memory"],
|
||||
pref: "enable-memory"
|
||||
},
|
||||
"memory-flamegraph": {
|
||||
id: "memory-flamegraph-view",
|
||||
view: MemoryFlameGraphView,
|
||||
requires: ["memory", "timeline"],
|
||||
pref: "enable-memory"
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -66,12 +88,13 @@ let DetailsView = {
|
|||
*/
|
||||
setAvailableViews: Task.async(function* () {
|
||||
let mocks = gFront.getMocksInUse();
|
||||
|
||||
for (let [name, { view, pref, requires }] of Iterator(this.components)) {
|
||||
let recording = PerformanceController.getCurrentRecording();
|
||||
|
||||
let isRecorded = recording && !recording.isRecording();
|
||||
// View is enabled view prefs
|
||||
let isEnabled = !pref || PerformanceController.getPref(pref);
|
||||
// View is enabled by its corresponding pref
|
||||
let isEnabled = !pref || PerformanceController.getOption(pref);
|
||||
// View is supported by the server actor, and the requried actor is not being mocked
|
||||
let isSupported = !requires || requires.every(r => !mocks[r]);
|
||||
|
||||
|
|
|
@ -40,8 +40,8 @@ let OverviewView = {
|
|||
|
||||
// Toggle the initial visibility of memory and framerate graph containers
|
||||
// based off of prefs.
|
||||
$("#memory-overview").hidden = !PerformanceController.getPref("enable-memory");
|
||||
$("#time-framerate").hidden = !PerformanceController.getPref("enable-framerate");
|
||||
$("#memory-overview").hidden = !PerformanceController.getOption("enable-memory");
|
||||
$("#time-framerate").hidden = !PerformanceController.getOption("enable-framerate");
|
||||
|
||||
PerformanceController.on(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.on(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
|
@ -55,6 +55,16 @@ let OverviewView = {
|
|||
* Unbinds events.
|
||||
*/
|
||||
destroy: function () {
|
||||
if (this.markersOverview) {
|
||||
this.markersOverview.destroy();
|
||||
}
|
||||
if (this.memoryOverview) {
|
||||
this.memoryOverview.destroy();
|
||||
}
|
||||
if (this.framerateGraph) {
|
||||
this.framerateGraph.destroy();
|
||||
}
|
||||
|
||||
PerformanceController.off(EVENTS.PREF_CHANGED, this._onPrefChanged);
|
||||
PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
|
||||
PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
|
||||
|
@ -86,7 +96,7 @@ let OverviewView = {
|
|||
* Sets the time interval selection for all graphs in this overview.
|
||||
*
|
||||
* @param object interval
|
||||
* The { starTime, endTime }, in milliseconds.
|
||||
* The { startTime, endTime }, in milliseconds.
|
||||
*/
|
||||
setTimeInterval: function(interval, options = {}) {
|
||||
if (this.isDisabled()) {
|
||||
|
@ -138,7 +148,8 @@ let OverviewView = {
|
|||
yield this.markersOverview.ready();
|
||||
return true;
|
||||
}
|
||||
this.markersOverview = new MarkersOverview($("#markers-overview"), TIMELINE_BLUEPRINT);
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.markersOverview = new MarkersOverview($("#markers-overview"), blueprint);
|
||||
this.markersOverview.headerHeight = MARKERS_GRAPH_HEADER_HEIGHT;
|
||||
this.markersOverview.rowHeight = MARKERS_GRAPH_ROW_HEIGHT;
|
||||
this.markersOverview.groupPadding = MARKERS_GROUP_VERTICAL_PADDING;
|
||||
|
@ -155,7 +166,7 @@ let OverviewView = {
|
|||
* ready to use, `false` if the graph is disabled.
|
||||
*/
|
||||
_memoryGraphAvailable: Task.async(function *() {
|
||||
if (!PerformanceController.getPref("enable-memory")) {
|
||||
if (!PerformanceController.getOption("enable-memory")) {
|
||||
return false;
|
||||
}
|
||||
if (this.memoryOverview) {
|
||||
|
@ -179,7 +190,7 @@ let OverviewView = {
|
|||
* ready to use, `false` if the graph is disabled.
|
||||
*/
|
||||
_framerateGraphAvailable: Task.async(function *() {
|
||||
if (!PerformanceController.getPref("enable-framerate")) {
|
||||
if (!PerformanceController.getOption("enable-framerate")) {
|
||||
return false;
|
||||
}
|
||||
if (this.framerateGraph) {
|
||||
|
@ -340,14 +351,26 @@ let OverviewView = {
|
|||
* Called whenever a preference in `devtools.performance.ui.` changes. Used
|
||||
* to toggle the visibility of memory and framerate graphs.
|
||||
*/
|
||||
_onPrefChanged: function (_, prefName) {
|
||||
if (prefName === "enable-memory") {
|
||||
$("#memory-overview").hidden = !PerformanceController.getPref("enable-memory");
|
||||
_onPrefChanged: Task.async(function* (_, prefName, prefValue) {
|
||||
switch (prefName) {
|
||||
case "enable-memory": {
|
||||
$("#memory-overview").hidden = !prefValue;
|
||||
break;
|
||||
}
|
||||
case "enable-framerate": {
|
||||
$("#time-framerate").hidden = !prefValue;
|
||||
break;
|
||||
}
|
||||
case "hidden-markers": {
|
||||
if (yield this._markersGraphAvailable()) {
|
||||
let blueprint = PerformanceController.getTimelineBlueprint();
|
||||
this.markersOverview.setBlueprint(blueprint);
|
||||
this.markersOverview.refresh({ force: true });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (prefName === "enable-framerate") {
|
||||
$("#time-framerate").hidden = !PerformanceController.getPref("enable-framerate");
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
toString: () => "[object OverviewView]"
|
||||
};
|
||||
|
|
|
@ -11,6 +11,9 @@ let ToolbarView = {
|
|||
* Sets up the view with event binding.
|
||||
*/
|
||||
initialize: Task.async(function *() {
|
||||
this._onFilterPopupShowing = this._onFilterPopupShowing.bind(this);
|
||||
this._onFilterPopupHiding = this._onFilterPopupHiding.bind(this);
|
||||
this._onHiddenMarkersChanged = this._onHiddenMarkersChanged.bind(this);
|
||||
this._onPrefChanged = this._onPrefChanged.bind(this);
|
||||
|
||||
this.optionsView = new OptionsView({
|
||||
|
@ -20,16 +23,87 @@ let ToolbarView = {
|
|||
|
||||
yield this.optionsView.initialize();
|
||||
this.optionsView.on("pref-changed", this._onPrefChanged);
|
||||
|
||||
this._buildMarkersFilterPopup();
|
||||
this._updateHiddenMarkersPopup();
|
||||
$("#performance-filter-menupopup").addEventListener("popupshowing", this._onFilterPopupShowing);
|
||||
$("#performance-filter-menupopup").addEventListener("popuphiding", this._onFilterPopupHiding);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Unbinds events and cleans up view.
|
||||
*/
|
||||
destroy: function () {
|
||||
$("#performance-filter-menupopup").removeEventListener("popupshowing", this._onFilterPopupShowing);
|
||||
$("#performance-filter-menupopup").removeEventListener("popuphiding", this._onFilterPopupHiding);
|
||||
|
||||
this.optionsView.off("pref-changed", this._onPrefChanged);
|
||||
this.optionsView.destroy();
|
||||
},
|
||||
|
||||
/**
|
||||
* Creates the timeline markers filter popup.
|
||||
*/
|
||||
_buildMarkersFilterPopup: function() {
|
||||
for (let [markerName, markerDetails] of Iterator(TIMELINE_BLUEPRINT)) {
|
||||
let menuitem = document.createElement("menuitem");
|
||||
menuitem.setAttribute("closemenu", "none");
|
||||
menuitem.setAttribute("type", "checkbox");
|
||||
menuitem.setAttribute("align", "center");
|
||||
menuitem.setAttribute("flex", "1");
|
||||
menuitem.setAttribute("label", markerDetails.label);
|
||||
menuitem.setAttribute("marker-type", markerName);
|
||||
|
||||
menuitem.addEventListener("command", this._onHiddenMarkersChanged);
|
||||
|
||||
// Style used by pseudo element ::before in performance.inc.css
|
||||
let bulletStyle = `--bullet-bg: ${markerDetails.fill};`
|
||||
bulletStyle += `--bullet-border: ${markerDetails.stroke}`;
|
||||
menuitem.setAttribute("style", bulletStyle);
|
||||
|
||||
$("#performance-filter-menupopup").appendChild(menuitem);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the menu items checked state in the timeline markers filter popup.
|
||||
*/
|
||||
_updateHiddenMarkersPopup: function() {
|
||||
let menuItems = $$("#performance-filter-menupopup menuitem[marker-type]");
|
||||
let hiddenMarkers = PerformanceController.getPref("hidden-markers");
|
||||
|
||||
for (let menuitem of menuItems) {
|
||||
if (~hiddenMarkers.indexOf(menuitem.getAttribute("marker-type"))) {
|
||||
menuitem.removeAttribute("checked");
|
||||
} else {
|
||||
menuitem.setAttribute("checked", "true");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the markers filter popup starts to show.
|
||||
*/
|
||||
_onFilterPopupShowing: function() {
|
||||
$("#filter-button").setAttribute("open", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when the markers filter popup starts to hide.
|
||||
*/
|
||||
_onFilterPopupHiding: function() {
|
||||
$("#filter-button").removeAttribute("open");
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a menu item in the markers filter popup is checked or unchecked.
|
||||
*/
|
||||
_onHiddenMarkersChanged: function() {
|
||||
let checkedMenuItems = $$("#performance-filter-menupopup menuitem[marker-type]:not([checked])");
|
||||
let hiddenMarkers = Array.map(checkedMenuItems, e => e.getAttribute("marker-type"));
|
||||
PerformanceController.setPref("hidden-markers", hiddenMarkers);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fired when a preference changes in the underlying OptionsView.
|
||||
* Propogated by the PerformanceController.
|
||||
|
|
|
@ -148,8 +148,6 @@ let PrefObserver = {
|
|||
this.branch.removeObserver("", this);
|
||||
},
|
||||
observe: function(subject, topic, pref) {
|
||||
Prefs.refresh();
|
||||
|
||||
if (pref == "ui.show-platform-data") {
|
||||
RecordingsListView.forceSelect(RecordingsListView.selectedItem);
|
||||
}
|
||||
|
|
|
@ -20,14 +20,23 @@ function test() {
|
|||
"The pref was was correctly changed (2).");
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.enabled", originalPrefValue);
|
||||
info("The pref value was reset.");
|
||||
|
||||
info("The pref value was reset (1).");
|
||||
is(Prefs.foo, !originalPrefValue,
|
||||
"The cached pref value hasn't changed yet.");
|
||||
"The cached pref value hasn't changed yet (1).");
|
||||
|
||||
Prefs.refresh();
|
||||
Services.prefs.setBoolPref("devtools.debugger.enabled", !originalPrefValue);
|
||||
info("The pref value was reset (2).");
|
||||
is(Prefs.foo, !originalPrefValue,
|
||||
"The cached pref value hasn't changed yet (2).");
|
||||
|
||||
Prefs.registerObserver();
|
||||
|
||||
Services.prefs.setBoolPref("devtools.debugger.enabled", originalPrefValue);
|
||||
info("The pref value was reset (3).");
|
||||
is(Prefs.foo, originalPrefValue,
|
||||
"The cached pref value has changed now.");
|
||||
|
||||
Prefs.unregisterObserver();
|
||||
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Timer.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"Heritage", "ViewHelpers", "WidgetMethods",
|
||||
|
@ -392,25 +393,57 @@ ViewHelpers.L10N.prototype = {
|
|||
* let prefs = new ViewHelpers.Prefs("root.path.to.branch", {
|
||||
* myIntPref: ["Int", "leaf.path.to.my-int-pref"],
|
||||
* myCharPref: ["Char", "leaf.path.to.my-char-pref"],
|
||||
* myJsonPref: ["Json", "leaf.path.to.my-json-pref"]
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* Get/set:
|
||||
* prefs.myCharPref = "foo";
|
||||
* let aux = prefs.myCharPref;
|
||||
*
|
||||
* Observe:
|
||||
* prefs.registerObserver();
|
||||
* prefs.on("pref-changed", (prefName, prefValue) => {
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* @param string aPrefsRoot
|
||||
* The root path to the required preferences branch.
|
||||
* @param object aPrefsObject
|
||||
* @param object aPrefsBlueprint
|
||||
* An object containing { accessorName: [prefType, prefName] } keys.
|
||||
*/
|
||||
ViewHelpers.Prefs = function(aPrefsRoot = "", aPrefsObject = {}) {
|
||||
this._root = aPrefsRoot;
|
||||
this._cache = new Map();
|
||||
ViewHelpers.Prefs = function(aPrefsRoot = "", aPrefsBlueprint = {}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
for (let accessorName in aPrefsObject) {
|
||||
let [prefType, prefName] = aPrefsObject[accessorName];
|
||||
this.map(accessorName, prefType, prefName);
|
||||
this._cache = new Map();
|
||||
let self = this;
|
||||
|
||||
for (let [accessorName, [prefType, prefName]] of Iterator(aPrefsBlueprint)) {
|
||||
this._map(accessorName, prefType, aPrefsRoot, prefName);
|
||||
}
|
||||
|
||||
let observer = {
|
||||
register: function() {
|
||||
this.branch = Services.prefs.getBranch(aPrefsRoot + ".");
|
||||
this.branch.addObserver("", this, false);
|
||||
},
|
||||
unregister: function() {
|
||||
this.branch.removeObserver("", this);
|
||||
},
|
||||
observe: function(_, __, aPrefName) {
|
||||
// If this particular pref isn't handled by the blueprint object,
|
||||
// even though it's in the specified branch, ignore it.
|
||||
let accessor = self._accessor(aPrefsBlueprint, aPrefName);
|
||||
if (!(accessor in self)) {
|
||||
return;
|
||||
}
|
||||
self._cache.delete(aPrefName);
|
||||
self.emit("pref-changed", accessor, self[accessor]);
|
||||
}
|
||||
};
|
||||
|
||||
this.registerObserver = () => observer.register();
|
||||
this.unregisterObserver = () => observer.unregister();
|
||||
};
|
||||
|
||||
ViewHelpers.Prefs.prototype = {
|
||||
|
@ -418,15 +451,16 @@ ViewHelpers.Prefs.prototype = {
|
|||
* Helper method for getting a pref value.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string aPrefsRoot
|
||||
* @param string aPrefName
|
||||
* @return any
|
||||
*/
|
||||
_get: function(aType, aPrefName) {
|
||||
_get: function(aType, aPrefsRoot, aPrefName) {
|
||||
let cachedPref = this._cache.get(aPrefName);
|
||||
if (cachedPref !== undefined) {
|
||||
return cachedPref;
|
||||
}
|
||||
let value = Services.prefs["get" + aType + "Pref"](aPrefName);
|
||||
let value = Services.prefs["get" + aType + "Pref"]([aPrefsRoot, aPrefName].join("."));
|
||||
this._cache.set(aPrefName, value);
|
||||
return value;
|
||||
},
|
||||
|
@ -435,11 +469,12 @@ ViewHelpers.Prefs.prototype = {
|
|||
* Helper method for setting a pref value.
|
||||
*
|
||||
* @param string aType
|
||||
* @param string aPrefsRoot
|
||||
* @param string aPrefName
|
||||
* @param any aValue
|
||||
*/
|
||||
_set: function(aType, aPrefName, aValue) {
|
||||
Services.prefs["set" + aType + "Pref"](aPrefName, aValue);
|
||||
_set: function(aType, aPrefsRoot, aPrefName, aValue) {
|
||||
Services.prefs["set" + aType + "Pref"]([aPrefsRoot, aPrefName].join("."), aValue);
|
||||
this._cache.set(aPrefName, aValue);
|
||||
},
|
||||
|
||||
|
@ -450,26 +485,36 @@ ViewHelpers.Prefs.prototype = {
|
|||
*
|
||||
* @param string aAccessorName
|
||||
* @param string aType
|
||||
* @param string aPrefsRoot
|
||||
* @param string aPrefName
|
||||
* @param array aSerializer
|
||||
*/
|
||||
map: function(aAccessorName, aType, aPrefName, aSerializer = { in: e => e, out: e => e }) {
|
||||
_map: function(aAccessorName, aType, aPrefsRoot, aPrefName, aSerializer = { in: e => e, out: e => e }) {
|
||||
if (aPrefName in this) {
|
||||
throw new Error(`Can't use ${aPrefName} because it's already a property.`);
|
||||
}
|
||||
if (aType == "Json") {
|
||||
this.map(aAccessorName, "Char", aPrefName, { in: JSON.parse, out: JSON.stringify });
|
||||
this._map(aAccessorName, "Char", aPrefsRoot, aPrefName, { in: JSON.parse, out: JSON.stringify });
|
||||
return;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, aAccessorName, {
|
||||
get: () => aSerializer.in(this._get(aType, [this._root, aPrefName].join("."))),
|
||||
set: (e) => this._set(aType, [this._root, aPrefName].join("."), aSerializer.out(e))
|
||||
get: () => aSerializer.in(this._get(aType, aPrefsRoot, aPrefName)),
|
||||
set: (e) => this._set(aType, aPrefsRoot, aPrefName, aSerializer.out(e))
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears all the cached preferences' values.
|
||||
* Finds the accessor in this object for the provided property name,
|
||||
* based on the blueprint object used in the constructor.
|
||||
*/
|
||||
refresh: function() {
|
||||
this._cache.clear();
|
||||
_accessor: function(aPrefsBlueprint, aPrefName) {
|
||||
for (let [accessorName, [, prefName]] of Iterator(aPrefsBlueprint)) {
|
||||
if (prefName == aPrefName) {
|
||||
return accessorName;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -138,9 +138,9 @@ function finishUp() {
|
|||
inspector.destroy();
|
||||
inspector = null;
|
||||
completer = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
});
|
||||
progress = null;
|
||||
progressDiv = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ support-files =
|
|||
head.js
|
||||
|
||||
[browser_timeline_aaa_run_first_leaktest.js]
|
||||
[browser_timeline_filters.js]
|
||||
[browser_timeline_overview-initial-selection-01.js]
|
||||
[browser_timeline_overview-initial-selection-02.js]
|
||||
[browser_timeline_overview-update.js]
|
||||
|
|
|
@ -49,9 +49,7 @@ add_task(function*() {
|
|||
ok(!isVisible($("#web-audio-editor-tabs")),
|
||||
"InspectorView tabs are still hidden.");
|
||||
|
||||
let nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
|
||||
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
|
||||
"Empty message hides even when loading node while open.");
|
||||
|
|
|
@ -1,63 +1,56 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
|
||||
/**
|
||||
* Test that the WebAudioInspector's Width is saved as
|
||||
* a preference
|
||||
*/
|
||||
|
||||
|
||||
add_task(function*() {
|
||||
let { target, panel } = yield initWebAudioEditor(SIMPLE_CONTEXT_URL);
|
||||
let { panelWin } = panel;
|
||||
let { gFront, $, $$, EVENTS, InspectorView } = panelWin;
|
||||
let gVars = InspectorView._propsView;
|
||||
|
||||
|
||||
let started = once(gFront, "start-context");
|
||||
|
||||
|
||||
reload(target);
|
||||
|
||||
|
||||
let [actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
let nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
|
||||
ok(!InspectorView.isVisible(), "InspectorView hidden on start.");
|
||||
|
||||
|
||||
// Open inspector pane
|
||||
$("#inspector-pane-toggle").click();
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
|
||||
|
||||
|
||||
let newInspectorWidth = 500;
|
||||
|
||||
|
||||
// Setting width to new_inspector_width
|
||||
$("#web-audio-inspector").setAttribute("width", newInspectorWidth);
|
||||
reload(target);
|
||||
|
||||
|
||||
//Width should be 500 after reloading
|
||||
[actors] = yield Promise.all([
|
||||
get3(gFront, "create-node"),
|
||||
waitForGraphRendered(panelWin, 3, 2)
|
||||
]);
|
||||
nodeIds = actors.map(actor => actor.actorID);
|
||||
|
||||
|
||||
// Open inspector pane
|
||||
$("#inspector-pane-toggle").click();
|
||||
yield once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED);
|
||||
|
||||
let nodeSet = Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, EVENTS.UI_PROPERTIES_TAB_RENDERED),
|
||||
once(panelWin, EVENTS.UI_AUTOMATION_TAB_RENDERED)
|
||||
]);
|
||||
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
|
||||
// Getting the width of the audio inspector
|
||||
let width = $("#web-audio-inspector").getAttribute("width");
|
||||
|
||||
|
||||
is(width, newInspectorWidth, "WebAudioEditor's Inspector width should be saved as a preference");
|
||||
|
||||
|
||||
yield teardown(target);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,12 +29,7 @@ add_task(function*() {
|
|||
"InspectorView tabs view should be hidden when no node's selected.");
|
||||
|
||||
// Wait for the node to be set as well as the inspector to come fully into the view
|
||||
let nodeSet = Promise.all([
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, EVENTS.UI_INSPECTOR_TOGGLED)
|
||||
]);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[1]));
|
||||
yield nodeSet;
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[1]), true);
|
||||
|
||||
ok(InspectorView.isVisible(), "InspectorView shown once node selected.");
|
||||
ok(!isVisible($("#web-audio-editor-details-pane-empty")),
|
||||
|
@ -45,9 +40,7 @@ add_task(function*() {
|
|||
is($("#web-audio-editor-tabs").selectedIndex, 0,
|
||||
"default tab selected should be the parameters tab.");
|
||||
|
||||
nodeSet = once(panelWin, EVENTS.UI_INSPECTOR_NODE_SET);
|
||||
click(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
yield nodeSet;
|
||||
yield clickGraphNode(panelWin, findGraphNode(panelWin, nodeIds[2]));
|
||||
|
||||
yield teardown(target);
|
||||
});
|
||||
|
|
|
@ -110,6 +110,7 @@ function once(aTarget, aEventName, aUseCapture = false) {
|
|||
if ((add in aTarget) && (remove in aTarget)) {
|
||||
aTarget[add](aEventName, function onEvent(...aArgs) {
|
||||
aTarget[remove](aEventName, onEvent, aUseCapture);
|
||||
info("Got event: '" + aEventName + "' on " + aTarget + ".");
|
||||
deferred.resolve(...aArgs);
|
||||
}, aUseCapture);
|
||||
break;
|
||||
|
@ -352,13 +353,15 @@ function wait (n) {
|
|||
|
||||
/**
|
||||
* Clicks a graph node based on actorID or passing in an element.
|
||||
* Returns a promise that resolves once
|
||||
* UI_INSPECTOR_NODE_SET is fired.
|
||||
* Returns a promise that resolves once UI_INSPECTOR_NODE_SET is fired and
|
||||
* the tabs have rendered, completing all RDP requests for the node.
|
||||
*/
|
||||
function clickGraphNode (panelWin, el, waitForToggle = false) {
|
||||
let { promise, resolve } = Promise.defer();
|
||||
let promises = [
|
||||
once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET)
|
||||
once(panelWin, panelWin.EVENTS.UI_INSPECTOR_NODE_SET),
|
||||
once(panelWin, panelWin.EVENTS.UI_PROPERTIES_TAB_RENDERED),
|
||||
once(panelWin, panelWin.EVENTS.UI_AUTOMATION_TAB_RENDERED)
|
||||
];
|
||||
|
||||
if (waitForToggle) {
|
||||
|
|
|
@ -39,7 +39,7 @@ let test = asyncTest(function* () {
|
|||
],
|
||||
});
|
||||
|
||||
testClickOpenNewTab(hud, results);
|
||||
yield testClickOpenNewTab(hud, results);
|
||||
});
|
||||
|
||||
function pushPrefEnv()
|
||||
|
@ -57,18 +57,5 @@ function testClickOpenNewTab(hud, results) {
|
|||
let warningNode = results[0].clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
|
||||
// Invoke the click event and check if a new tab would open to the correct page.
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == LEARN_MORE_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -43,11 +43,11 @@ let test = asyncTest(function* () {
|
|||
],
|
||||
});
|
||||
|
||||
testClickOpenNewTab(hud, results[0]);
|
||||
yield testClickOpenNewTab(hud, results[0]);
|
||||
|
||||
let results2 = yield mixedContentOverrideTest2(hud, browser);
|
||||
|
||||
testClickOpenNewTab(hud, results2[0]);
|
||||
yield testClickOpenNewTab(hud, results2[0]);
|
||||
});
|
||||
|
||||
function pushPrefEnv()
|
||||
|
@ -118,19 +118,5 @@ function testClickOpenNewTab(hud, match) {
|
|||
let warningNode = match.clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
|
||||
// Invoke the click event and check if a new tab would
|
||||
// open to the correct page.
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == LEARN_MORE_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
///////////////////
|
||||
//
|
||||
// Whitelisting this test.
|
||||
// As part of bug 1077403, the leaking uncaught rejection should be fixed.
|
||||
//
|
||||
thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Connection closed");
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug-632275-getters.html";
|
||||
|
||||
let getterValue = null;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
const TEST_URI = "data:text/html;charset=utf8,Web Console mixed content test";
|
||||
const TEST_HTTPS_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-bug-737873-mixedcontent.html";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/MixedContent";
|
||||
|
||||
let test = asyncTest(function* () {
|
||||
Services.prefs.setBoolPref("security.mixed_content.block_display_content", false);
|
||||
|
@ -27,45 +28,33 @@ let test = asyncTest(function* () {
|
|||
Services.prefs.clearUserPref("security.mixed_content.block_active_content");
|
||||
});
|
||||
|
||||
function testMixedContent(hud) {
|
||||
let testMixedContent = Task.async(function*(hud) {
|
||||
content.location = TEST_HTTPS_URI;
|
||||
|
||||
return waitForMessages({
|
||||
let results = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [{
|
||||
text: "example.com",
|
||||
category: CATEGORY_NETWORK,
|
||||
severity: SEVERITY_WARNING,
|
||||
}],
|
||||
}).then((results) => {
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "page load logged");
|
||||
ok(msg.classList.contains("mixed-content"), ".mixed-content element");
|
||||
|
||||
let link = msg.querySelector(".learn-more-link");
|
||||
ok(link, "mixed content link element");
|
||||
is(link.textContent, "[Mixed Content]", "link text is accurate");
|
||||
|
||||
let oldOpenLink = hud.openLink;
|
||||
let linkOpened = false;
|
||||
hud.openLink = (url) => {
|
||||
is(url, "https://developer.mozilla.org/docs/Security/MixedContent",
|
||||
"url opened");
|
||||
linkOpened = true;
|
||||
};
|
||||
|
||||
EventUtils.synthesizeMouse(link, 2, 2, {}, link.ownerDocument.defaultView);
|
||||
|
||||
ok(linkOpened, "clicking the Mixed Content link opened a page");
|
||||
|
||||
hud.openLink = oldOpenLink;
|
||||
|
||||
ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
|
||||
|
||||
hud.setFilterState("netwarn", false);
|
||||
|
||||
ok(msg.classList.contains("filtered-by-type"), "message is filtered");
|
||||
|
||||
hud.setFilterState("netwarn", true);
|
||||
});
|
||||
}
|
||||
|
||||
let msg = [...results[0].matched][0];
|
||||
ok(msg, "page load logged");
|
||||
ok(msg.classList.contains("mixed-content"), ".mixed-content element");
|
||||
|
||||
let link = msg.querySelector(".learn-more-link");
|
||||
ok(link, "mixed content link element");
|
||||
is(link.textContent, "[Mixed Content]", "link text is accurate");
|
||||
|
||||
yield simulateMessageLinkClick(link, LEARN_MORE_URI);
|
||||
|
||||
ok(!msg.classList.contains("filtered-by-type"), "message is not filtered");
|
||||
|
||||
hud.setFilterState("netwarn", false);
|
||||
|
||||
ok(msg.classList.contains("filtered-by-type"), "message is filtered");
|
||||
|
||||
hud.setFilterState("netwarn", true);
|
||||
});
|
||||
|
|
|
@ -40,26 +40,12 @@ let test = asyncTest(function* () {
|
|||
],
|
||||
});
|
||||
|
||||
testClickOpenNewTab(hud, result);
|
||||
yield testClickOpenNewTab(hud, result);
|
||||
});
|
||||
|
||||
function testClickOpenNewTab(hud, [result]) {
|
||||
let msg = [...result.matched][0];
|
||||
let warningNode = msg.querySelector(".learn-more-link");
|
||||
ok(warningNode, "learn more link");
|
||||
|
||||
// Invoke the click event and check if a new tab would open to the correct
|
||||
// page
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == INSECURE_PASSWORDS_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Insecure Passwords Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
return simulateMessageLinkClick(warningNode, INSECURE_PASSWORDS_URI);
|
||||
}
|
||||
|
|
|
@ -6,45 +6,30 @@ const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/t
|
|||
const HSTS_INVALID_HEADER_MSG = "The site specified an invalid Strict-Transport-Security header.";
|
||||
const LEARN_MORE_URI = "https://developer.mozilla.org/docs/Security/HTTP_Strict_Transport_Security";
|
||||
|
||||
function test()
|
||||
{
|
||||
loadTab(TEST_URI).then(() => {
|
||||
openConsole().then((hud) => {
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "Invalid HSTS header error displayed successfully",
|
||||
text: HSTS_INVALID_HEADER_MSG,
|
||||
category: CATEGORY_SECURITY,
|
||||
severity: SEVERITY_WARNING,
|
||||
objects: true,
|
||||
},
|
||||
],
|
||||
}).then((results) => testClickOpenNewTab(hud, results));
|
||||
})
|
||||
let test = asyncTest(function* () {
|
||||
yield loadTab(TEST_URI);
|
||||
|
||||
let hud = yield openConsole();
|
||||
|
||||
let results = yield waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
name: "Invalid HSTS header error displayed successfully",
|
||||
text: HSTS_INVALID_HEADER_MSG,
|
||||
category: CATEGORY_SECURITY,
|
||||
severity: SEVERITY_WARNING,
|
||||
objects: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
yield testClickOpenNewTab(hud, results);
|
||||
});
|
||||
|
||||
function testClickOpenNewTab(hud, results) {
|
||||
let warningNode = results[0].clickableElements[0];
|
||||
ok(warningNode, "link element");
|
||||
ok(warningNode.classList.contains("learn-more-link"), "link class name");
|
||||
|
||||
// Invoke the click event and check if a new tab would
|
||||
// open to the correct page.
|
||||
let linkOpened = false;
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(aLink) {
|
||||
if (aLink == LEARN_MORE_URI) {
|
||||
linkOpened = true;
|
||||
}
|
||||
}
|
||||
|
||||
EventUtils.synthesizeMouse(warningNode, 2, 2, {},
|
||||
warningNode.ownerDocument.defaultView);
|
||||
ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
|
||||
finishTest();
|
||||
return simulateMessageLinkClick(warningNode, LEARN_MORE_URI);
|
||||
}
|
||||
|
|
|
@ -1646,3 +1646,32 @@ function getSourceActor(aSources, aURL) {
|
|||
let item = aSources.getItemForAttachment(a => a.source.url === aURL);
|
||||
return item && item.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that clicking on a link from a popup notification message tries to
|
||||
* open the expected URL.
|
||||
*/
|
||||
function simulateMessageLinkClick(element, expectedLink) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
// Invoke the click event and check if a new tab would
|
||||
// open to the correct page.
|
||||
let oldOpenUILinkIn = window.openUILinkIn;
|
||||
window.openUILinkIn = function(link) {
|
||||
if (link == expectedLink) {
|
||||
ok(true, "Clicking the message link opens the desired page");
|
||||
window.openUILinkIn = oldOpenUILinkIn;
|
||||
deferred.resolve();
|
||||
}
|
||||
};
|
||||
|
||||
let event = new MouseEvent("click", {
|
||||
detail: 1,
|
||||
button: 0,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
element.dispatchEvent(event);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
|
|
@ -153,6 +153,15 @@ function loadAddonManager() {
|
|||
startupManager();
|
||||
}
|
||||
|
||||
// Starts the addon manager without creating app info. We can't directly use
|
||||
// |loadAddonManager| defined above in test_conditions.js as it would make the test fail.
|
||||
function startAddonManagerOnly() {
|
||||
let addonManager = Cc["@mozilla.org/addons/integration;1"]
|
||||
.getService(Ci.nsIObserver)
|
||||
.QueryInterface(Ci.nsITimerCallback);
|
||||
addonManager.observe(null, "addons-startup", null);
|
||||
}
|
||||
|
||||
function getExperimentAddons(previous=false) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
|
||||
|
||||
Cu.import("resource:///modules/experiments/Experiments.jsm");
|
||||
Cu.import("resource://gre/modules/TelemetryPing.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "gDatareportingService",
|
||||
() => Cc["@mozilla.org/datareporting/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject);
|
||||
|
||||
const FILE_MANIFEST = "experiments.manifest";
|
||||
const SEC_IN_ONE_DAY = 24 * 60 * 60;
|
||||
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
|
||||
|
@ -45,6 +51,17 @@ function applicableFromManifestData(data, policy) {
|
|||
return entry.isApplicable();
|
||||
}
|
||||
|
||||
function initialiseTelemetry() {
|
||||
// Send the needed startup notifications to the datareporting service
|
||||
// to ensure that it has been initialized.
|
||||
if ("@mozilla.org/datareporting/service;1" in Cc) {
|
||||
gDatareportingService.observe(null, "app-startup", null);
|
||||
gDatareportingService.observe(null, "profile-after-change", null);
|
||||
}
|
||||
|
||||
return TelemetryPing.setup().then(TelemetrySession.setup);
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
@ -52,7 +69,8 @@ function run_test() {
|
|||
add_task(function* test_setup() {
|
||||
createAppInfo();
|
||||
gProfileDir = do_get_profile();
|
||||
yield TelemetrySession.setup();
|
||||
startAddonManagerOnly();
|
||||
yield initialiseTelemetry();
|
||||
gPolicy = new Experiments.Policy();
|
||||
|
||||
gReporter = yield getReporter("json_payload_simple");
|
||||
|
@ -312,5 +330,5 @@ add_task(function* test_times() {
|
|||
});
|
||||
|
||||
add_task(function* test_shutdown() {
|
||||
yield TelemetrySession.shutdown();
|
||||
yield TelemetrySession.shutdown(false);
|
||||
});
|
||||
|
|
|
@ -64,9 +64,13 @@
|
|||
- on the "+" (new tab) button for a profile when a selection is available. -->
|
||||
<!ENTITY profilerUI.newtab.tooltiptext "Add new tab from selection">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.toolbar.filter.tooltiptext): This string
|
||||
- is displayed next to the filter button-->
|
||||
<!ENTITY profilerUI.options.filter.tooltiptext "Select what data to display in the timeline">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.options.tooltiptext): This is the tooltip
|
||||
- for the options button. -->
|
||||
<!ENTITY profilerUI.options.tooltiptext "Configure performance preferences.">
|
||||
<!ENTITY profilerUI.options.gear.tooltiptext "Configure performance preferences.">
|
||||
|
||||
<!-- LOCALIZATION NOTE (profilerUI.invertTree): This is the label shown next to
|
||||
- a checkbox that inverts and un-inverts the profiler's call tree. -->
|
||||
|
|
|
@ -25,6 +25,10 @@
|
|||
-moz-border-end-color: var(--theme-splitter-color);
|
||||
}
|
||||
|
||||
#performance-toolbar-control-other {
|
||||
-moz-padding-end: 5px;
|
||||
}
|
||||
|
||||
#performance-toolbar-controls-detail-views > toolbarbutton {
|
||||
min-width: 0;
|
||||
}
|
||||
|
@ -34,6 +38,31 @@
|
|||
-moz-padding-end: 8px;
|
||||
}
|
||||
|
||||
#filter-button {
|
||||
list-style-image: url(timeline-filter.svg#filter);
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
#filter-button[disabled] {
|
||||
list-style-image: url(timeline-filter.svg#filter-disabled);
|
||||
}
|
||||
|
||||
#filter-button[open] {
|
||||
list-style-image: url(timeline-filter.svg#filter-open);
|
||||
}
|
||||
|
||||
#performance-filter-menupopup > menuitem:before {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
margin: 0 8px;
|
||||
border: 1px solid;
|
||||
border-radius: 1px;
|
||||
background-color: var(--bullet-bg);
|
||||
border-color: var(--bullet-border);
|
||||
}
|
||||
|
||||
/* Recording Notice */
|
||||
|
||||
#performance-view .notice-container {
|
||||
|
|
До Ширина: | Высота: | Размер: 14 KiB После Ширина: | Высота: | Размер: 14 KiB |
До Ширина: | Высота: | Размер: 7.6 KiB После Ширина: | Высота: | Размер: 7.4 KiB |
До Ширина: | Высота: | Размер: 21 KiB После Ширина: | Высота: | Размер: 20 KiB |
До Ширина: | Высота: | Размер: 31 KiB После Ширина: | Высота: | Размер: 36 KiB |
Двоичные данные
mobile/android/branding/beta/res/drawable-xxhdpi/widget_icon.png
До Ширина: | Высота: | Размер: 18 KiB После Ширина: | Высота: | Размер: 17 KiB |
|
@ -33,12 +33,48 @@ body {
|
|||
font-family: serif;
|
||||
}
|
||||
|
||||
.font-size1 {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.font-size2 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.font-size3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.font-size4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.font-size5 {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.font-size6 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.font-size7 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.font-size8 {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.font-size9 {
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 40px;
|
||||
display: none;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.header {
|
||||
|
@ -65,7 +101,7 @@ body {
|
|||
}
|
||||
|
||||
.header > h1 {
|
||||
font-size: 2.625em;
|
||||
font-size: 1.33rem;
|
||||
font-weight: 700;
|
||||
line-height: 1.1em;
|
||||
width: 100%;
|
||||
|
@ -107,66 +143,19 @@ body {
|
|||
color: #aaaaaa;
|
||||
}
|
||||
|
||||
.font-size1 > body > .header > h1 {
|
||||
font-size: 27px;
|
||||
}
|
||||
|
||||
.font-size2 > body > .header > h1 {
|
||||
font-size: 29px;
|
||||
}
|
||||
|
||||
.font-size3 > body > .header > h1 {
|
||||
font-size: 31px;
|
||||
}
|
||||
|
||||
.font-size4 > body > .header > h1 {
|
||||
font-size: 33px;
|
||||
}
|
||||
|
||||
.font-size5 > body > .header > h1 {
|
||||
font-size: 35px;
|
||||
}
|
||||
|
||||
/* This covers caption, domain, and credits
|
||||
texts in the reader UI */
|
||||
|
||||
.font-size1 > body > .content .wp-caption-text,
|
||||
.font-size1 > body > .content figcaption,
|
||||
.font-size1 > body > .header > .domain,
|
||||
.font-size1 > body > .header > .credits {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.font-size2 > body > .content .wp-caption-text,
|
||||
.font-size2 > body > .content figcaption,
|
||||
.font-size2 > body > .header > .domain,
|
||||
.font-size2 > body > .header > .credits {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.font-size3 > body > .content .wp-caption-text,
|
||||
.font-size3 > body > .content figcaption,
|
||||
.font-size3 > body > .header > .domain,
|
||||
.font-size3 > body > .header > .credits {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.font-size4 > body > .content .wp-caption-text,
|
||||
.font-size4 > body > .content figcaption,
|
||||
.font-size4 > body > .header > .domain,
|
||||
.font-size4 > body > .header > .credits {
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.font-size5 > body > .content .wp-caption-text,
|
||||
.font-size5 > body > .content figcaption,
|
||||
.font-size5 > body > .header > .domain,
|
||||
.font-size5 > body > .header > .credits {
|
||||
font-size: 19px;
|
||||
.content .wp-caption-text,
|
||||
.content figcaption,
|
||||
.header > .domain,
|
||||
.header > .credits {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: none;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.content a {
|
||||
|
@ -295,31 +284,6 @@ body {
|
|||
list-style: decimal !important;
|
||||
}
|
||||
|
||||
.font-size1-sample,
|
||||
.font-size1 > body > .content {
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.font-size2-sample,
|
||||
.font-size2 > body > .content {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
.font-size3-sample,
|
||||
.font-size3 > body > .content {
|
||||
font-size: 18px !important;
|
||||
}
|
||||
|
||||
.font-size4-sample,
|
||||
.font-size4 > body > .content {
|
||||
font-size: 20px !important;
|
||||
}
|
||||
|
||||
.font-size5-sample,
|
||||
.font-size5 > body > .content {
|
||||
font-size: 22px !important;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
font-family: "Clear Sans",sans-serif;
|
||||
transition-property: visibility, opacity;
|
||||
|
@ -361,7 +325,8 @@ body {
|
|||
}
|
||||
|
||||
/* Remove dotted border when button is focused */
|
||||
.button::-moz-focus-inner {
|
||||
.button::-moz-focus-inner,
|
||||
#font-size-buttons > button::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
@ -427,6 +392,7 @@ body {
|
|||
}
|
||||
|
||||
#font-type-buttons,
|
||||
#font-size-buttons,
|
||||
.segmented-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
@ -436,13 +402,22 @@ body {
|
|||
}
|
||||
|
||||
#font-type-buttons > li,
|
||||
#font-size-buttons > button,
|
||||
.segmented-button > li {
|
||||
width: 50px; /* combined with flex, this acts as a minimum width */
|
||||
flex: 1 0 auto;
|
||||
text-align: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
#font-type-buttons > li,
|
||||
#font-size-buttons > button {
|
||||
width: 100px; /* combined with flex, this acts as a minimum width */
|
||||
}
|
||||
|
||||
.segmented-button > li {
|
||||
width: 50px; /* combined with flex, this acts as a minimum width */
|
||||
}
|
||||
|
||||
#font-type-buttons > li {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
@ -451,6 +426,7 @@ body {
|
|||
border-left: 1px solid #B5B5B5;
|
||||
}
|
||||
|
||||
#font-size-buttons > button:first-child,
|
||||
.segmented-button > li:first-child {
|
||||
border-left: 0px;
|
||||
}
|
||||
|
@ -496,6 +472,24 @@ body {
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.minus-button,
|
||||
.plus-button {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
height: 30px;
|
||||
background-size: 18px 18px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.minus-button {
|
||||
background-size: 24px 6px;
|
||||
}
|
||||
|
||||
.plus-button {
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
/* desktop-only controls */
|
||||
.close-button,
|
||||
.list-button {
|
||||
|
@ -518,6 +512,14 @@ body {
|
|||
background-image: url('chrome://browser/skin/images/reader-style-icon-mdpi.png');
|
||||
}
|
||||
|
||||
.minus-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-minus-mdpi.png');
|
||||
}
|
||||
|
||||
.plus-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-plus-mdpi.png');
|
||||
}
|
||||
|
||||
@media screen and (min-resolution: 1.25dppx) {
|
||||
.dropdown-arrow {
|
||||
background-image: url('chrome://browser/skin/images/reader-dropdown-arrow-hdpi.png');
|
||||
|
@ -538,6 +540,14 @@ body {
|
|||
.style-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-style-icon-hdpi.png');
|
||||
}
|
||||
|
||||
.minus-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-minus-hdpi.png');
|
||||
}
|
||||
|
||||
.plus-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-plus-hdpi.png');
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-resolution: 2dppx) {
|
||||
|
@ -560,6 +570,14 @@ body {
|
|||
.style-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-style-icon-xhdpi.png');
|
||||
}
|
||||
|
||||
.minus-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-minus-xhdpi.png');
|
||||
}
|
||||
|
||||
.plus-button {
|
||||
background-image: url('chrome://browser/skin/images/reader-plus-xhdpi.png');
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (orientation: portrait) {
|
||||
|
|
После Ширина: | Высота: | Размер: 195 B |
После Ширина: | Высота: | Размер: 155 B |
После Ширина: | Высота: | Размер: 223 B |
После Ширина: | Высота: | Размер: 506 B |
После Ширина: | Высота: | Размер: 394 B |
После Ширина: | Высота: | Размер: 609 B |
|
@ -89,6 +89,12 @@ chrome.jar:
|
|||
skin/images/reader-dropdown-arrow-mdpi.png (images/reader-dropdown-arrow-mdpi.png)
|
||||
skin/images/reader-dropdown-arrow-hdpi.png (images/reader-dropdown-arrow-hdpi.png)
|
||||
skin/images/reader-dropdown-arrow-xhdpi.png (images/reader-dropdown-arrow-xhdpi.png)
|
||||
skin/images/reader-minus-mdpi.png (images/reader-minus-mdpi.png)
|
||||
skin/images/reader-minus-hdpi.png (images/reader-minus-hdpi.png)
|
||||
skin/images/reader-minus-xhdpi.png (images/reader-minus-xhdpi.png)
|
||||
skin/images/reader-plus-mdpi.png (images/reader-plus-mdpi.png)
|
||||
skin/images/reader-plus-hdpi.png (images/reader-plus-hdpi.png)
|
||||
skin/images/reader-plus-xhdpi.png (images/reader-plus-xhdpi.png)
|
||||
skin/images/reader-toggle-on-icon-mdpi.png (images/reader-toggle-on-icon-mdpi.png)
|
||||
skin/images/reader-toggle-on-icon-hdpi.png (images/reader-toggle-on-icon-hdpi.png)
|
||||
skin/images/reader-toggle-on-icon-xhdpi.png (images/reader-toggle-on-icon-xhdpi.png)
|
||||
|
|
|
@ -4560,8 +4560,8 @@ pref("reader.parse-on-load.enabled", true);
|
|||
// is disabled by default.
|
||||
pref("reader.parse-on-load.force-enabled", false);
|
||||
|
||||
// The default relative font size in reader mode (1-5)
|
||||
pref("reader.font_size", 3);
|
||||
// The default relative font size in reader mode (1-9)
|
||||
pref("reader.font_size", 5);
|
||||
|
||||
// The default color scheme in reader mode (light, dark, sepia, auto)
|
||||
// auto = color automatically adjusts according to ambient light level
|
||||
|
|
|
@ -230,6 +230,11 @@ user_pref('browser.tiles.reportURL', 'http://%(server)s/tests/robocop/robocop_ti
|
|||
// We want to collect telemetry, but we don't want to send in the results.
|
||||
user_pref('toolkit.telemetry.server', 'https://%(server)s/telemetry-dummy/');
|
||||
|
||||
// A couple of preferences with default values to test that telemetry preference
|
||||
// watching is working.
|
||||
user_pref('toolkit.telemetry.test.pref1', true);
|
||||
user_pref('toolkit.telemetry.test.pref2', false);
|
||||
|
||||
// We don't want to hit the real Firefox Accounts server for tests. We don't
|
||||
// actually need a functioning FxA server, so just set it to something that
|
||||
// resolves and accepts requests, even if they all fail.
|
||||
|
|
|
@ -169,6 +169,29 @@ var PrintUtils = {
|
|||
let printSettings = this.getPrintSettings();
|
||||
|
||||
let mm = aBrowser.messageManager;
|
||||
|
||||
mm.addMessageListener("Printing:Print:Done", function onPrintingDone(msg) {
|
||||
mm.removeMessageListener("Printing:Print:Done", onPrintingDone);
|
||||
let rv = msg.data.rv;
|
||||
let printSettings = msg.objects.printSettings;
|
||||
if (rv != Components.results.NS_OK &&
|
||||
rv != Components.results.NS_ERROR_ABORT) {
|
||||
Cu.reportError(`In Printing:Print:Done handler, got unexpected rv
|
||||
${rv}.`);
|
||||
}
|
||||
|
||||
if (gPrintSettingsAreGlobal && gSavePrintSettings) {
|
||||
let PSSVC =
|
||||
Components.classes["@mozilla.org/gfx/printsettings-service;1"]
|
||||
.getService(Components.interfaces.nsIPrintSettingsService);
|
||||
|
||||
PSSVC.savePrintSettingsToPrefs(printSettings, true,
|
||||
printSettings.kInitSaveAll);
|
||||
PSSVC.savePrintSettingsToPrefs(printSettings, false,
|
||||
printSettings.kInitSavePrinterName);
|
||||
}
|
||||
});
|
||||
|
||||
mm.sendAsyncMessage("Printing:Print", null, {
|
||||
printSettings: printSettings,
|
||||
contentWindow: aWindow,
|
||||
|
|
|
@ -74,43 +74,22 @@ let AboutReader = function(mm, win) {
|
|||
|
||||
let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
|
||||
let fontTypeOptions = [
|
||||
{ name: fontTypeSample,
|
||||
description: gStrings.GetStringFromName("aboutReader.fontType.serif"),
|
||||
value: "serif",
|
||||
linkClass: "serif" },
|
||||
{ name: fontTypeSample,
|
||||
description: gStrings.GetStringFromName("aboutReader.fontType.sans-serif"),
|
||||
value: "sans-serif",
|
||||
linkClass: "sans-serif"
|
||||
},
|
||||
{ name: fontTypeSample,
|
||||
description: gStrings.GetStringFromName("aboutReader.fontType.serif"),
|
||||
value: "serif",
|
||||
linkClass: "serif" },
|
||||
];
|
||||
|
||||
let fontType = Services.prefs.getCharPref("reader.font_type");
|
||||
this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
|
||||
this._setFontType(fontType);
|
||||
|
||||
let fontSizeSample = gStrings.GetStringFromName("aboutReader.fontSizeSample");
|
||||
let fontSizeOptions = [
|
||||
{ name: fontSizeSample,
|
||||
value: 1,
|
||||
linkClass: "font-size1-sample" },
|
||||
{ name: fontSizeSample,
|
||||
value: 2,
|
||||
linkClass: "font-size2-sample" },
|
||||
{ name: fontSizeSample,
|
||||
value: 3,
|
||||
linkClass: "font-size3-sample" },
|
||||
{ name: fontSizeSample,
|
||||
value: 4,
|
||||
linkClass: "font-size4-sample" },
|
||||
{ name: fontSizeSample,
|
||||
value: 5,
|
||||
linkClass: "font-size5-sample" }
|
||||
];
|
||||
|
||||
let fontSize = Services.prefs.getIntPref("reader.font_size");
|
||||
this._setupSegmentedButton("font-size-buttons", fontSizeOptions, fontSize, this._setFontSize.bind(this));
|
||||
this._setFontSize(fontSize);
|
||||
this._setupFontSizeButtons();
|
||||
|
||||
// Track status of reader toolbar add/remove toggle button
|
||||
this._isReadingListItem = -1;
|
||||
|
@ -307,6 +286,63 @@ AboutReader.prototype = {
|
|||
});
|
||||
},
|
||||
|
||||
_setupFontSizeButtons: function() {
|
||||
const FONT_SIZE_MIN = 1;
|
||||
const FONT_SIZE_MAX = 9;
|
||||
|
||||
let currentSize = Services.prefs.getIntPref("reader.font_size");
|
||||
currentSize = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, currentSize));
|
||||
|
||||
let plusButton = this._doc.getElementById("font-size-plus");
|
||||
let minusButton = this._doc.getElementById("font-size-minus");
|
||||
|
||||
function updateControls() {
|
||||
if (currentSize === FONT_SIZE_MIN) {
|
||||
minusButton.setAttribute("disabled", true);
|
||||
} else {
|
||||
minusButton.removeAttribute("disabled");
|
||||
}
|
||||
if (currentSize === FONT_SIZE_MAX) {
|
||||
plusButton.setAttribute("disabled", true);
|
||||
} else {
|
||||
plusButton.removeAttribute("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
updateControls();
|
||||
this._setFontSize(currentSize);
|
||||
|
||||
plusButton.addEventListener("click", (event) => {
|
||||
if (!event.isTrusted) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
|
||||
if (currentSize >= FONT_SIZE_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSize++;
|
||||
updateControls();
|
||||
this._setFontSize(currentSize);
|
||||
}, true);
|
||||
|
||||
minusButton.addEventListener("click", (event) => {
|
||||
if (!event.isTrusted) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation();
|
||||
|
||||
if (currentSize <= FONT_SIZE_MIN) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentSize--;
|
||||
updateControls();
|
||||
this._setFontSize(currentSize);
|
||||
}, true);
|
||||
},
|
||||
|
||||
_handleDeviceLight: function Reader_handleDeviceLight(newLux) {
|
||||
// Desired size of the this._luxValues array.
|
||||
let luxValuesSize = 10;
|
||||
|
|
|
@ -32,7 +32,10 @@
|
|||
<li class="dropdown-popup">
|
||||
<ul id="font-type-buttons"></ul>
|
||||
<hr></hr>
|
||||
<ul id="font-size-buttons" class="segmented-button"></ul>
|
||||
<div id="font-size-buttons">
|
||||
<button id="font-size-minus" class="minus-button"/>
|
||||
<button id="font-size-plus" class="plus-button"/>
|
||||
</div>
|
||||
<hr></hr>
|
||||
<ul id="color-scheme-buttons" class="segmented-button"></ul>
|
||||
<div class="dropdown-arrow"/>
|
||||
|
|
|
@ -42,3 +42,14 @@ $(histogram_data_file): $(histograms_file) $(data_python_deps)
|
|||
$(PYTHON) $(srcdir)/gen-histogram-data.py $< > $@
|
||||
|
||||
GARBAGE += $(histogram_enum_file)
|
||||
|
||||
# This is so hacky. Waiting on bug 988938.
|
||||
addondir = $(srcdir)/tests/addons
|
||||
testdir = $(abspath $(DEPTH)/_tests/xpcshell/toolkit/components/telemetry/tests/unit)
|
||||
|
||||
misc:: $(call mkdir_deps,$(testdir))
|
||||
$(EXIT_ON_ERROR) \
|
||||
for dir in $(addondir)/*; do \
|
||||
base=`basename $$dir`; \
|
||||
(cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
|
||||
done
|
||||
|
|
|
@ -84,6 +84,9 @@ using base::LinearHistogram;
|
|||
using base::StatisticsRecorder;
|
||||
|
||||
const char KEYED_HISTOGRAM_NAME_SEPARATOR[] = "#";
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
const char SUBSESSION_HISTOGRAM_PREFIX[] = "sub#";
|
||||
#endif
|
||||
|
||||
enum reflectStatus {
|
||||
REFLECT_OK,
|
||||
|
@ -699,6 +702,10 @@ private:
|
|||
nsresult GetHistogramByName(const nsACString &name, Histogram **ret);
|
||||
bool ShouldReflectHistogram(Histogram *h);
|
||||
void IdentifyCorruptHistograms(StatisticsRecorder::Histograms &hs);
|
||||
nsresult CreateHistogramSnapshots(JSContext *cx,
|
||||
JS::MutableHandle<JS::Value> ret,
|
||||
bool subsession,
|
||||
bool clearSubsession);
|
||||
typedef StatisticsRecorder::Histograms::iterator HistogramIterator;
|
||||
|
||||
struct AddonHistogramInfo {
|
||||
|
@ -774,18 +781,24 @@ public:
|
|||
KeyedHistogram(const nsACString &name, const nsACString &expiration,
|
||||
uint32_t histogramType, uint32_t min, uint32_t max,
|
||||
uint32_t bucketCount);
|
||||
nsresult GetHistogram(const nsCString& name, Histogram** histogram);
|
||||
Histogram* GetHistogram(const nsCString& name);
|
||||
nsresult GetHistogram(const nsCString& name, Histogram** histogram, bool subsession);
|
||||
Histogram* GetHistogram(const nsCString& name, bool subsession);
|
||||
uint32_t GetHistogramType() const { return mHistogramType; }
|
||||
nsresult GetDataset(uint32_t* dataset) const;
|
||||
nsresult GetJSKeys(JSContext* cx, JS::CallArgs& args);
|
||||
nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj);
|
||||
void Clear();
|
||||
nsresult GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
bool subsession, bool clearSubsession);
|
||||
|
||||
nsresult Add(const nsCString& key, uint32_t aSample);
|
||||
void Clear(bool subsession);
|
||||
|
||||
private:
|
||||
typedef nsBaseHashtableET<nsCStringHashKey, Histogram*> KeyedHistogramEntry;
|
||||
typedef AutoHashtable<KeyedHistogramEntry> KeyedHistogramMapType;
|
||||
KeyedHistogramMapType mHistogramMap;
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
KeyedHistogramMapType mSubsessionMap;
|
||||
#endif
|
||||
|
||||
struct ReflectKeysArgs {
|
||||
JSContext* jsContext;
|
||||
|
@ -991,6 +1004,93 @@ GetHistogramByEnumId(Telemetry::ID id, Histogram **ret)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* This clones a histogram |existing| with the id |existingId| to a
|
||||
* new histogram with the name |newName|.
|
||||
* For simplicity this is limited to registered histograms.
|
||||
*/
|
||||
Histogram*
|
||||
CloneHistogram(const nsACString& newName, Telemetry::ID existingId,
|
||||
Histogram& existing)
|
||||
{
|
||||
const TelemetryHistogram &info = gHistograms[existingId];
|
||||
Histogram *clone = nullptr;
|
||||
nsresult rv;
|
||||
|
||||
rv = HistogramGet(PromiseFlatCString(newName).get(), info.expiration(),
|
||||
info.histogramType, existing.declared_min(),
|
||||
existing.declared_max(), existing.bucket_count(),
|
||||
true, &clone);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Histogram::SampleSet ss;
|
||||
existing.SnapshotSample(&ss);
|
||||
clone->AddSampleSet(ss);
|
||||
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* This clones a histogram with the id |existingId| to a new histogram
|
||||
* with the name |newName|.
|
||||
* For simplicity this is limited to registered histograms.
|
||||
*/
|
||||
Histogram*
|
||||
CloneHistogram(const nsACString& newName, Telemetry::ID existingId)
|
||||
{
|
||||
Histogram *existing = nullptr;
|
||||
nsresult rv = GetHistogramByEnumId(existingId, &existing);
|
||||
if (NS_FAILED(rv)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return CloneHistogram(newName, existingId, *existing);
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
Histogram*
|
||||
GetSubsessionHistogram(Histogram& existing)
|
||||
{
|
||||
Telemetry::ID id;
|
||||
nsresult rv = TelemetryImpl::GetHistogramEnumId(existing.histogram_name().c_str(), &id);
|
||||
if (NS_FAILED(rv) || gHistograms[id].keyed) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Histogram* subsession[Telemetry::HistogramCount] = {};
|
||||
if (subsession[id]) {
|
||||
return subsession[id];
|
||||
}
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(prefix, SUBSESSION_HISTOGRAM_PREFIX);
|
||||
nsDependentCString existingName(gHistograms[id].id());
|
||||
if (StringBeginsWith(existingName, prefix)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsCString subsessionName(prefix);
|
||||
subsessionName.Append(existingName);
|
||||
|
||||
subsession[id] = CloneHistogram(subsessionName, id, existing);
|
||||
return subsession[id];
|
||||
}
|
||||
#endif
|
||||
|
||||
nsresult
|
||||
HistogramAdd(Histogram& histogram, int32_t value)
|
||||
{
|
||||
histogram.Add(value);
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (Histogram* subsession = GetSubsessionHistogram(histogram)) {
|
||||
subsession->Add(value);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
FillRanges(JSContext *cx, JS::Handle<JSObject*> array, Histogram *h)
|
||||
{
|
||||
|
@ -1089,6 +1189,7 @@ JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
}
|
||||
|
||||
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
|
||||
MOZ_ASSERT(h);
|
||||
Histogram::ClassType type = h->histogram_type();
|
||||
|
||||
int32_t value = 1;
|
||||
|
@ -1110,7 +1211,7 @@ JSHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
}
|
||||
|
||||
if (TelemetryImpl::CanRecord()) {
|
||||
h->Add(value);
|
||||
HistogramAdd(*h, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -1152,8 +1253,32 @@ JSHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool onlySubsession = false;
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
||||
|
||||
if (args.length() >= 1) {
|
||||
if (!args[0].isBoolean()) {
|
||||
JS_ReportError(cx, "Not a boolean");
|
||||
return false;
|
||||
}
|
||||
|
||||
onlySubsession = JS::ToBoolean(args[0]);
|
||||
}
|
||||
#endif
|
||||
|
||||
Histogram *h = static_cast<Histogram*>(JS_GetPrivate(obj));
|
||||
h->Clear();
|
||||
MOZ_ASSERT(h);
|
||||
if(!onlySubsession) {
|
||||
h->Clear();
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (Histogram* subsession = GetSubsessionHistogram(*h)) {
|
||||
subsession->Clear();
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1243,17 +1368,7 @@ JSKeyedHistogram_Add(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
}
|
||||
}
|
||||
|
||||
Histogram* h = nullptr;
|
||||
nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h);
|
||||
if (NS_FAILED(rv)) {
|
||||
JS_ReportError(cx, "Failed to get histogram");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TelemetryImpl::CanRecord()) {
|
||||
h->Add(value);
|
||||
}
|
||||
|
||||
keyed->Add(NS_ConvertUTF16toUTF8(key), value);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1275,7 +1390,8 @@ JSKeyedHistogram_Keys(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
}
|
||||
|
||||
bool
|
||||
JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
KeyedHistogram_SnapshotImpl(JSContext *cx, unsigned argc, JS::Value *vp,
|
||||
bool subsession, bool clearSubsession)
|
||||
{
|
||||
JSObject *obj = JS_THIS_OBJECT(cx, vp);
|
||||
if (!obj) {
|
||||
|
@ -1296,7 +1412,7 @@ JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot))) {
|
||||
if (!NS_SUCCEEDED(keyed->GetJSSnapshot(cx, snapshot, subsession, clearSubsession))) {
|
||||
JS_ReportError(cx, "Failed to reflect keyed histograms");
|
||||
return false;
|
||||
}
|
||||
|
@ -1312,7 +1428,7 @@ JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
}
|
||||
|
||||
Histogram* h = nullptr;
|
||||
nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h);
|
||||
nsresult rv = keyed->GetHistogram(NS_ConvertUTF16toUTF8(key), &h, subsession);
|
||||
if (NS_FAILED(rv)) {
|
||||
JS_ReportError(cx, "Failed to get histogram");
|
||||
return false;
|
||||
|
@ -1337,6 +1453,33 @@ JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
}
|
||||
}
|
||||
|
||||
bool
|
||||
JSKeyedHistogram_Snapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
{
|
||||
return KeyedHistogram_SnapshotImpl(cx, argc, vp, false, false);
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
bool
|
||||
JSKeyedHistogram_SubsessionSnapshot(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
{
|
||||
return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
bool
|
||||
JSKeyedHistogram_SnapshotSubsessionAndClear(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
{
|
||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
||||
if (args.length() != 0) {
|
||||
JS_ReportError(cx, "No key arguments supported for snapshotSubsessionAndClear");
|
||||
}
|
||||
|
||||
return KeyedHistogram_SnapshotImpl(cx, argc, vp, true, true);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool
|
||||
JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
{
|
||||
|
@ -1350,7 +1493,23 @@ JSKeyedHistogram_Clear(JSContext *cx, unsigned argc, JS::Value *vp)
|
|||
return false;
|
||||
}
|
||||
|
||||
keyed->Clear();
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
bool onlySubsession = false;
|
||||
JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
|
||||
|
||||
if (args.length() >= 1) {
|
||||
if (!(args[0].isNumber() || args[0].isBoolean())) {
|
||||
JS_ReportError(cx, "Not a boolean");
|
||||
return false;
|
||||
}
|
||||
|
||||
onlySubsession = JS::ToBoolean(args[0]);
|
||||
}
|
||||
|
||||
keyed->Clear(onlySubsession);
|
||||
#else
|
||||
keyed->Clear(false);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1391,6 +1550,10 @@ WrapAndReturnKeyedHistogram(KeyedHistogram *h, JSContext *cx, JS::MutableHandle<
|
|||
return NS_ERROR_FAILURE;
|
||||
if (!(JS_DefineFunction(cx, obj, "add", JSKeyedHistogram_Add, 2, 0)
|
||||
&& JS_DefineFunction(cx, obj, "snapshot", JSKeyedHistogram_Snapshot, 1, 0)
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
&& JS_DefineFunction(cx, obj, "subsessionSnapshot", JSKeyedHistogram_SubsessionSnapshot, 1, 0)
|
||||
&& JS_DefineFunction(cx, obj, "snapshotSubsessionAndClear", JSKeyedHistogram_SnapshotSubsessionAndClear, 0, 0)
|
||||
#endif
|
||||
&& JS_DefineFunction(cx, obj, "keys", JSKeyedHistogram_Keys, 0, 0)
|
||||
&& JS_DefineFunction(cx, obj, "clear", JSKeyedHistogram_Clear, 0, 0)
|
||||
&& JS_DefineFunction(cx, obj, "dataset", JSKeyedHistogram_Dataset, 0, 0))) {
|
||||
|
@ -1667,6 +1830,18 @@ mFailedLockCount(0)
|
|||
mTrackedDBs.MarkImmutable();
|
||||
#endif
|
||||
|
||||
// Populate the static histogram name->id cache.
|
||||
// Note that the histogram names are statically allocated.
|
||||
for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) {
|
||||
CharPtrEntryType *entry = mHistogramMap.PutEntry(gHistograms[i].id());
|
||||
entry->mData = (Telemetry::ID) i;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
mHistogramMap.MarkImmutable();
|
||||
#endif
|
||||
|
||||
// Create registered keyed histograms
|
||||
for (size_t i = 0; i < ArrayLength(gHistograms); ++i) {
|
||||
const TelemetryHistogram& h = gHistograms[i];
|
||||
if (!h.keyed) {
|
||||
|
@ -1796,21 +1971,8 @@ TelemetryImpl::GetHistogramEnumId(const char *name, Telemetry::ID *id)
|
|||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Cache names
|
||||
// Note the histogram names are statically allocated
|
||||
TelemetryImpl::HistogramMapType *map = &sTelemetry->mHistogramMap;
|
||||
if (!map->Count()) {
|
||||
for (uint32_t i = 0; i < Telemetry::HistogramCount; i++) {
|
||||
CharPtrEntryType *entry = map->PutEntry(gHistograms[i].id());
|
||||
if (MOZ_UNLIKELY(!entry)) {
|
||||
map->Clear();
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
entry->mData = (Telemetry::ID) i;
|
||||
}
|
||||
}
|
||||
|
||||
CharPtrEntryType *entry = map->GetEntry(name);
|
||||
const TelemetryImpl::HistogramMapType& map = sTelemetry->mHistogramMap;
|
||||
CharPtrEntryType *entry = map.GetEntry(name);
|
||||
if (!entry) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
@ -1843,25 +2005,12 @@ TelemetryImpl::HistogramFrom(const nsACString &name, const nsACString &existing_
|
|||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
const TelemetryHistogram &p = gHistograms[id];
|
||||
|
||||
Histogram *existing;
|
||||
rv = GetHistogramByEnumId(id, &existing);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
Histogram* clone = CloneHistogram(name, id);
|
||||
if (!clone) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
Histogram *clone;
|
||||
rv = HistogramGet(PromiseFlatCString(name).get(), p.expiration(),
|
||||
p.histogramType, existing->declared_min(),
|
||||
existing->declared_max(), existing->bucket_count(),
|
||||
true, &clone);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
Histogram::SampleSet ss;
|
||||
existing->SnapshotSample(&ss);
|
||||
clone->AddSampleSet(ss);
|
||||
return WrapAndReturnHistogram(clone, cx, ret);
|
||||
}
|
||||
|
||||
|
@ -2044,8 +2193,11 @@ TelemetryImpl::UnregisterAddonHistograms(const nsACString &id)
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
|
||||
nsresult
|
||||
TelemetryImpl::CreateHistogramSnapshots(JSContext *cx,
|
||||
JS::MutableHandle<JS::Value> ret,
|
||||
bool subsession,
|
||||
bool clearSubsession)
|
||||
{
|
||||
JS::Rooted<JSObject*> root_obj(cx, JS_NewPlainObject(cx));
|
||||
if (!root_obj)
|
||||
|
@ -2086,6 +2238,16 @@ TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value>
|
|||
continue;
|
||||
}
|
||||
|
||||
Histogram* original = h;
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (subsession) {
|
||||
h = GetSubsessionHistogram(*h);
|
||||
if (!h) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
hobj = JS_NewPlainObject(cx);
|
||||
if (!hobj) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -2099,15 +2261,39 @@ TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value>
|
|||
case REFLECT_FAILURE:
|
||||
return NS_ERROR_FAILURE;
|
||||
case REFLECT_OK:
|
||||
if (!JS_DefineProperty(cx, root_obj, h->histogram_name().c_str(), hobj,
|
||||
JSPROP_ENUMERATE)) {
|
||||
if (!JS_DefineProperty(cx, root_obj, original->histogram_name().c_str(),
|
||||
hobj, JSPROP_ENUMERATE)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (subsession && clearSubsession) {
|
||||
h->Clear();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::GetHistogramSnapshots(JSContext *cx, JS::MutableHandle<JS::Value> ret)
|
||||
{
|
||||
return CreateHistogramSnapshots(cx, ret, false, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TelemetryImpl::SnapshotSubsessionHistograms(bool clearSubsession,
|
||||
JSContext *cx,
|
||||
JS::MutableHandle<JS::Value> ret)
|
||||
{
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
return CreateHistogramSnapshots(cx, ret, true, clearSubsession);
|
||||
#else
|
||||
return NS_OK;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
TelemetryImpl::CreateHistogramForAddon(const nsACString &name,
|
||||
AddonHistogramInfo &info)
|
||||
|
@ -2213,7 +2399,7 @@ TelemetryImpl::KeyedHistogramsReflector(const nsACString& key, nsAutoPtr<KeyedHi
|
|||
return PL_DHASH_STOP;
|
||||
}
|
||||
|
||||
if (!NS_SUCCEEDED(entry->GetJSSnapshot(cx, snapshot))) {
|
||||
if (!NS_SUCCEEDED(entry->GetJSSnapshot(cx, snapshot, false, false))) {
|
||||
return PL_DHASH_STOP;
|
||||
}
|
||||
|
||||
|
@ -3338,7 +3524,7 @@ Accumulate(ID aHistogram, uint32_t aSample)
|
|||
Histogram *h;
|
||||
nsresult rv = GetHistogramByEnumId(aHistogram, &h);
|
||||
if (NS_SUCCEEDED(rv))
|
||||
h->Add(aSample);
|
||||
HistogramAdd(*h, aSample);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -3351,11 +3537,7 @@ Accumulate(ID aID, const nsCString& aKey, uint32_t aSample)
|
|||
const TelemetryHistogram& th = gHistograms[aID];
|
||||
KeyedHistogram* keyed = TelemetryImpl::GetKeyedHistogramById(nsDependentCString(th.id()));
|
||||
MOZ_ASSERT(keyed);
|
||||
|
||||
Histogram* histogram = keyed->GetHistogram(aKey);
|
||||
if (histogram) {
|
||||
histogram->Add(aSample);
|
||||
}
|
||||
keyed->Add(aKey, aSample);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -3373,7 +3555,7 @@ Accumulate(const char* name, uint32_t sample)
|
|||
Histogram *h;
|
||||
rv = GetHistogramByEnumId(id, &h);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
h->Add(sample);
|
||||
HistogramAdd(*h, sample);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3776,6 +3958,9 @@ KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expirat
|
|||
uint32_t histogramType, uint32_t min, uint32_t max,
|
||||
uint32_t bucketCount)
|
||||
: mHistogramMap()
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
, mSubsessionMap()
|
||||
#endif
|
||||
, mName(name)
|
||||
, mExpiration(expiration)
|
||||
, mHistogramType(histogramType)
|
||||
|
@ -3786,15 +3971,27 @@ KeyedHistogram::KeyedHistogram(const nsACString &name, const nsACString &expirat
|
|||
}
|
||||
|
||||
nsresult
|
||||
KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram)
|
||||
KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram,
|
||||
bool subsession)
|
||||
{
|
||||
KeyedHistogramEntry* entry = mHistogramMap.GetEntry(key);
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
|
||||
#else
|
||||
KeyedHistogramMapType& map = mHistogramMap;
|
||||
#endif
|
||||
KeyedHistogramEntry* entry = map.GetEntry(key);
|
||||
if (entry) {
|
||||
*histogram = entry->mData;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCString histogramName = mName;
|
||||
nsCString histogramName;
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (subsession) {
|
||||
histogramName.Append(SUBSESSION_HISTOGRAM_PREFIX);
|
||||
}
|
||||
#endif
|
||||
histogramName.Append(mName);
|
||||
histogramName.Append(KEYED_HISTOGRAM_NAME_SEPARATOR);
|
||||
histogramName.Append(key);
|
||||
|
||||
|
@ -3810,7 +4007,7 @@ KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram)
|
|||
h->SetFlags(Histogram::kExtendedStatisticsFlag);
|
||||
*histogram = h;
|
||||
|
||||
entry = mHistogramMap.PutEntry(key);
|
||||
entry = map.PutEntry(key);
|
||||
if (MOZ_UNLIKELY(!entry)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
@ -3820,10 +4017,10 @@ KeyedHistogram::GetHistogram(const nsCString& key, Histogram** histogram)
|
|||
}
|
||||
|
||||
Histogram*
|
||||
KeyedHistogram::GetHistogram(const nsCString& key)
|
||||
KeyedHistogram::GetHistogram(const nsCString& key, bool subsession)
|
||||
{
|
||||
Histogram* h = nullptr;
|
||||
if (NS_FAILED(GetHistogram(key, &h))) {
|
||||
if (NS_FAILED(GetHistogram(key, &h, subsession))) {
|
||||
return nullptr;
|
||||
}
|
||||
return h;
|
||||
|
@ -3852,9 +4049,44 @@ KeyedHistogram::ClearHistogramEnumerator(KeyedHistogramEntry* entry, void*)
|
|||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
void
|
||||
KeyedHistogram::Clear()
|
||||
nsresult
|
||||
KeyedHistogram::Add(const nsCString& key, uint32_t sample)
|
||||
{
|
||||
if (!TelemetryImpl::CanRecord()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
Histogram* histogram = GetHistogram(key, false);
|
||||
MOZ_ASSERT(histogram);
|
||||
if (!histogram) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
Histogram* subsession = GetHistogram(key, true);
|
||||
MOZ_ASSERT(subsession);
|
||||
if (!subsession) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
#endif
|
||||
|
||||
histogram->Add(sample);
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
subsession->Add(sample);
|
||||
#endif
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
KeyedHistogram::Clear(bool onlySubsession)
|
||||
{
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
mSubsessionMap.EnumerateEntries(&KeyedHistogram::ClearHistogramEnumerator, nullptr);
|
||||
mSubsessionMap.Clear();
|
||||
if (onlySubsession) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
mHistogramMap.EnumerateEntries(&KeyedHistogram::ClearHistogramEnumerator, nullptr);
|
||||
mHistogramMap.Clear();
|
||||
}
|
||||
|
@ -3920,11 +4152,23 @@ KeyedHistogram::ReflectKeyedHistogram(KeyedHistogramEntry* entry, JSContext* cx,
|
|||
}
|
||||
|
||||
nsresult
|
||||
KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj)
|
||||
KeyedHistogram::GetJSSnapshot(JSContext* cx, JS::Handle<JSObject*> obj,
|
||||
bool subsession, bool clearSubsession)
|
||||
{
|
||||
if (!mHistogramMap.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
KeyedHistogramMapType& map = subsession ? mSubsessionMap : mHistogramMap;
|
||||
#else
|
||||
KeyedHistogramMapType& map = mHistogramMap;
|
||||
#endif
|
||||
if (!map.ReflectIntoJS(&KeyedHistogram::ReflectKeyedHistogram, cx, obj)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
|
||||
if (subsession && clearSubsession) {
|
||||
Clear(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
|
|
@ -244,13 +244,9 @@ this.TelemetryFile = {
|
|||
*
|
||||
* @return {iterator}
|
||||
*/
|
||||
popPendingPings: function*(reason) {
|
||||
popPendingPings: function*() {
|
||||
while (pendingPings.length > 0) {
|
||||
let data = pendingPings.pop();
|
||||
// Send persisted pings to the test URL too.
|
||||
if (reason == "test-ping") {
|
||||
data.reason = reason;
|
||||
}
|
||||
yield data;
|
||||
}
|
||||
},
|
||||
|
@ -263,7 +259,9 @@ this.TelemetryFile = {
|
|||
|
||||
///// Utility functions
|
||||
function pingFilePath(ping) {
|
||||
return OS.Path.join(TelemetryFile.pingDirectoryPath, ping.slug);
|
||||
// Support legacy ping formats, who don't have an "id" field, but a "slug" field.
|
||||
let pingIdentifier = (ping.slug) ? ping.slug : ping.id;
|
||||
return OS.Path.join(TelemetryFile.pingDirectoryPath, pingIdentifier);
|
||||
}
|
||||
|
||||
function getPingDirectory() {
|
||||
|
|
|
@ -30,10 +30,14 @@ const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump";
|
|||
const PREF_CACHED_CLIENTID = PREF_BRANCH + "cachedClientID"
|
||||
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
|
||||
|
||||
const PING_FORMAT_VERSION = 4;
|
||||
|
||||
// Delay before intializing telemetry (ms)
|
||||
const TELEMETRY_DELAY = 60000;
|
||||
// Delay before initializing telemetry if we're testing (ms)
|
||||
const TELEMETRY_TEST_DELAY = 100;
|
||||
// The number of days to keep pings serialised on the disk in case of failures.
|
||||
const DEFAULT_RETENTION_DAYS = 14;
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
|
||||
"@mozilla.org/base/telemetry;1",
|
||||
|
@ -46,6 +50,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
|
|||
"resource://gre/modules/TelemetryLog.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe",
|
||||
"resource://gre/modules/ThirdPartyCookieProbe.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel",
|
||||
"resource://gre/modules/UpdateChannel.jsm");
|
||||
|
||||
/**
|
||||
* Setup Telemetry logging. This function also gets called when loggin related
|
||||
|
@ -86,6 +94,14 @@ function generateUUID() {
|
|||
return str.substring(1, str.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the ping has new ping format or a legacy one.
|
||||
*/
|
||||
function isNewPingFormat(aPing) {
|
||||
return ("id" in aPing) && ("application" in aPing) &&
|
||||
("version" in aPing) && (aPing.version >= 2);
|
||||
}
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["TelemetryPing"];
|
||||
|
||||
this.TelemetryPing = Object.freeze({
|
||||
|
@ -131,9 +147,105 @@ this.TelemetryPing = Object.freeze({
|
|||
|
||||
/**
|
||||
* Send payloads to the server.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} [aOptions] Options object.
|
||||
* @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
|
||||
* id, false otherwise.
|
||||
* @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
|
||||
* environment data.
|
||||
* @returns {Promise} A promise that resolves when the ping is sent.
|
||||
*/
|
||||
send: function(aReason, aPingPayload) {
|
||||
return Impl.send(aReason, aPingPayload);
|
||||
send: function(aType, aPayload, aOptions = {}) {
|
||||
let options = aOptions;
|
||||
options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS;
|
||||
options.addClientId = aOptions.addClientId || false;
|
||||
options.addEnvironment = aOptions.addEnvironment || false;
|
||||
|
||||
return Impl.send(aType, aPayload, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add the ping to the pending ping list and save all pending pings.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} [aOptions] Options object.
|
||||
* @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
|
||||
* id, false otherwise.
|
||||
* @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
|
||||
* environment data.
|
||||
* @returns {Promise} A promise that resolves when the pings are saved.
|
||||
*/
|
||||
savePendingPings: function(aType, aPayload, aOptions = {}) {
|
||||
let options = aOptions;
|
||||
options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS;
|
||||
options.addClientId = aOptions.addClientId || false;
|
||||
options.addEnvironment = aOptions.addEnvironment || false;
|
||||
|
||||
return Impl.savePendingPings(aType, aPayload, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Save a ping to disk.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} [aOptions] Options object.
|
||||
* @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
|
||||
* id, false otherwise.
|
||||
* @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
|
||||
* environment data.
|
||||
* @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name,
|
||||
* if found.
|
||||
*
|
||||
* @returns {Promise} A promise that resolves when the ping is saved to disk.
|
||||
*/
|
||||
savePing: function(aType, aPayload, aOptions = {}) {
|
||||
let options = aOptions;
|
||||
options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS;
|
||||
options.addClientId = aOptions.addClientId || false;
|
||||
options.addEnvironment = aOptions.addEnvironment || false;
|
||||
options.overwrite = aOptions.overwrite || false;
|
||||
|
||||
return Impl.savePing(aType, aPayload, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Only used for testing. Saves a ping to disk and return the ping id once done.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} [aOptions] Options object.
|
||||
* @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} [aOptions.addClientId=false] true if the ping should contain the client
|
||||
* id, false otherwise.
|
||||
* @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
|
||||
* environment data.
|
||||
* @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name,
|
||||
* if found.
|
||||
* @param {String} [aOptions.filePath] The path to save the ping to. Will save to default
|
||||
* ping location if not provided.
|
||||
*
|
||||
* @returns {Promise<Integer>} A promise that resolves with the ping id when the ping is
|
||||
* saved to disk.
|
||||
*/
|
||||
testSavePingToFile: function(aType, aPayload, aOptions = {}) {
|
||||
let options = aOptions;
|
||||
options.retentionDays = aOptions.retentionDays || DEFAULT_RETENTION_DAYS;
|
||||
options.addClientId = aOptions.addClientId || false;
|
||||
options.addEnvironment = aOptions.addEnvironment || false;
|
||||
options.overwrite = aOptions.overwrite || false;
|
||||
|
||||
return Impl.testSavePingToFile(aType, aPayload, options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -144,23 +256,111 @@ this.TelemetryPing = Object.freeze({
|
|||
get clientID() {
|
||||
return Impl.clientID;
|
||||
},
|
||||
|
||||
/**
|
||||
* The AsyncShutdown.Barrier to synchronize with TelemetryPing shutdown.
|
||||
*/
|
||||
get shutdown() {
|
||||
return Impl._shutdownBarrier.client;
|
||||
},
|
||||
});
|
||||
|
||||
let Impl = {
|
||||
_initialized: false,
|
||||
_initStarted: false, // Whether we started setting up TelemetryPing.
|
||||
_log: null,
|
||||
_prevValues: {},
|
||||
// The previous build ID, if this is the first run with a new build.
|
||||
// Undefined if this is not the first run, or the previous build ID is unknown.
|
||||
_previousBuildID: undefined,
|
||||
_clientID: null,
|
||||
// A task performing delayed initialization
|
||||
_delayedInitTask: null,
|
||||
// The deferred promise resolved when the initialization task completes.
|
||||
_delayedInitTaskDeferred: null,
|
||||
|
||||
popPayloads: function popPayloads(reason, externalPayload) {
|
||||
_shutdownBarrier: new AsyncShutdown.Barrier("TelemetryPing: Waiting for clients."),
|
||||
|
||||
/**
|
||||
* Get the data for the "application" section of the ping.
|
||||
*/
|
||||
_getApplicationSection: function() {
|
||||
// Querying architecture and update channel can throw. Make sure to recover and null
|
||||
// those fields.
|
||||
let arch = null;
|
||||
try {
|
||||
arch = Services.sysinfo.get("arch");
|
||||
} catch (e) {
|
||||
this._log.trace("assemblePing - Unable to get system architecture.", e);
|
||||
}
|
||||
|
||||
let updateChannel = null;
|
||||
try {
|
||||
updateChannel = UpdateChannel.get();
|
||||
} catch (e) {
|
||||
this._log.trace("assemblePing - Unable to get update channel.", e);
|
||||
}
|
||||
|
||||
return {
|
||||
architecture: arch,
|
||||
buildId: Services.appinfo.appBuildID,
|
||||
name: Services.appinfo.name,
|
||||
version: Services.appinfo.version,
|
||||
vendor: Services.appinfo.vendor,
|
||||
platformVersion: Services.appinfo.platformVersion,
|
||||
xpcomAbi: Services.appinfo.XPCOMABI,
|
||||
channel: updateChannel,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Assemble a complete ping following the common ping format specification.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} aOptions Options object.
|
||||
* @param {Boolean} aOptions.addClientId true if the ping should contain the client
|
||||
* id, false otherwise.
|
||||
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
|
||||
* environment data.
|
||||
*
|
||||
* @returns Promise<Object> A promise that resolves when the ping is completely assembled.
|
||||
*/
|
||||
assemblePing: function assemblePing(aType, aPayload, aOptions = {}) {
|
||||
this._log.trace("assemblePing - Type " + aType + ", Server " + this._server +
|
||||
", aOptions " + JSON.stringify(aOptions));
|
||||
|
||||
// Fill the common ping fields.
|
||||
let pingData = {
|
||||
type: aType,
|
||||
id: generateUUID(),
|
||||
creationDate: (new Date()).toISOString(),
|
||||
version: PING_FORMAT_VERSION,
|
||||
application: this._getApplicationSection(),
|
||||
payload: aPayload,
|
||||
};
|
||||
|
||||
if (aOptions.addClientId) {
|
||||
pingData.clientId = this._clientID;
|
||||
}
|
||||
|
||||
if (aOptions.addEnvironment) {
|
||||
return TelemetryEnvironment.getEnvironmentData().then(environment => {
|
||||
pingData.environment = environment;
|
||||
return pingData;
|
||||
},
|
||||
error => {
|
||||
this._log.error("assemblePing - Rejection", error);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(pingData);
|
||||
},
|
||||
|
||||
popPayloads: function popPayloads() {
|
||||
this._log.trace("popPayloads");
|
||||
function payloadIter() {
|
||||
if (externalPayload && reason != "overdue-flush") {
|
||||
yield externalPayload;
|
||||
}
|
||||
let iterator = TelemetryFile.popPendingPings(reason);
|
||||
let iterator = TelemetryFile.popPendingPings();
|
||||
for (let data of iterator) {
|
||||
yield data;
|
||||
}
|
||||
|
@ -178,29 +378,142 @@ let Impl = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Send data to the server. Record success/send-time in histograms
|
||||
* Build a complete ping and send data to the server. Record success/send-time in
|
||||
* histograms.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} aOptions Options object.
|
||||
* @param {Number} aOptions.retentionDays The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
|
||||
* false otherwise.
|
||||
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
|
||||
* environment data.
|
||||
*
|
||||
* @returns {Promise} A promise that resolves when the ping is sent.
|
||||
*/
|
||||
send: function send(reason, aPayload) {
|
||||
this._log.trace("send - Reason " + reason + ", Server " + this._server);
|
||||
return this.sendPingsFromIterator(this._server, reason,
|
||||
Iterator(this.popPayloads(reason, aPayload)));
|
||||
send: function send(aType, aPayload, aOptions) {
|
||||
this._log.trace("send - Type " + aType + ", Server " + this._server +
|
||||
", aOptions " + JSON.stringify(aOptions));
|
||||
|
||||
return this.assemblePing(aType, aPayload, aOptions)
|
||||
.then(pingData => {
|
||||
// Once ping is assembled, send it along with the persisted ping in the backlog.
|
||||
let p = [
|
||||
// Persist the ping if sending it fails.
|
||||
this.doPing(pingData, false)
|
||||
.catch(() => TelemetryFile.savePing(pingData, true)),
|
||||
this.sendPersistedPings(),
|
||||
];
|
||||
return Promise.all(p);
|
||||
},
|
||||
error => this._log.error("send - Rejection", error));
|
||||
},
|
||||
|
||||
sendPingsFromIterator: function sendPingsFromIterator(server, reason, i) {
|
||||
let p = [data for (data in i)].map((data) =>
|
||||
this.doPing(server, data).then(null, () => TelemetryFile.savePing(data, true)));
|
||||
|
||||
/**
|
||||
* Send the persisted pings to the server.
|
||||
*/
|
||||
sendPersistedPings: function sendPersistedPings() {
|
||||
this._log.trace("sendPersistedPings");
|
||||
let pingsIterator = Iterator(this.popPayloads());
|
||||
let p = [data for (data in pingsIterator)].map(data => this.doPing(data, true));
|
||||
return Promise.all(p);
|
||||
},
|
||||
|
||||
finishPingRequest: function finishPingRequest(success, startTime, ping) {
|
||||
/**
|
||||
* Saves all the pending pings, plus the passed one, to disk.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} aOptions Options object.
|
||||
* @param {Number} aOptions.retentionDays The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
|
||||
* false otherwise.
|
||||
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
|
||||
* environment data.
|
||||
*
|
||||
* @returns {Promise} A promise that resolves when all the pings are saved to disk.
|
||||
*/
|
||||
savePendingPings: function savePendingPings(aType, aPayload, aOptions) {
|
||||
this._log.trace("savePendingPings - Type " + aType + ", Server " + this._server +
|
||||
", aOptions " + JSON.stringify(aOptions));
|
||||
|
||||
return this.assemblePing(aType, aPayload, aOptions)
|
||||
.then(pingData => TelemetryFile.savePendingPings(pingData),
|
||||
error => this._log.error("savePendingPings - Rejection", error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Save a ping to disk.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} aOptions Options object.
|
||||
* @param {Number} aOptions.retentionDays The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
|
||||
* false otherwise.
|
||||
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
|
||||
* environment data.
|
||||
* @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found.
|
||||
*
|
||||
* @returns {Promise} A promise that resolves when the ping is saved to disk.
|
||||
*/
|
||||
savePing: function savePing(aType, aPayload, aOptions) {
|
||||
this._log.trace("savePing - Type " + aType + ", Server " + this._server +
|
||||
", aOptions " + JSON.stringify(aOptions));
|
||||
|
||||
return this.assemblePing(aType, aPayload, aOptions)
|
||||
.then(pingData => TelemetryFile.savePing(pingData, aOptions.overwrite),
|
||||
error => this._log.error("savePing - Rejection", error));
|
||||
},
|
||||
|
||||
/**
|
||||
* Save a ping to disk and return the ping id when done.
|
||||
*
|
||||
* @param {String} aType The type of the ping.
|
||||
* @param {Object} aPayload The actual data payload for the ping.
|
||||
* @param {Object} aOptions Options object.
|
||||
* @param {Number} aOptions.retentionDays The number of days to keep the ping on disk
|
||||
* if sending fails.
|
||||
* @param {Boolean} aOptions.addClientId true if the ping should contain the client id,
|
||||
* false otherwise.
|
||||
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
|
||||
* environment data.
|
||||
* @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found.
|
||||
* @param {String} [aOptions.filePath] The path to save the ping to. Will save to default
|
||||
* ping location if not provided.
|
||||
*
|
||||
* @returns {Promise} A promise that resolves with the ping id when the ping is saved to
|
||||
* disk.
|
||||
*/
|
||||
testSavePingToFile: function testSavePingToFile(aType, aPayload, aOptions) {
|
||||
this._log.trace("testSavePingToFile - Type " + aType + ", Server " + this._server +
|
||||
", aOptions " + JSON.stringify(aOptions));
|
||||
return this.assemblePing(aType, aPayload, aOptions)
|
||||
.then(pingData => {
|
||||
if (aOptions.filePath) {
|
||||
return TelemetryFile.savePingToFile(pingData, aOptions.filePath, aOptions.overwrite)
|
||||
.then(() => { return pingData.id; });
|
||||
} else {
|
||||
return TelemetryFile.savePing(pingData, aOptions.overwrite)
|
||||
.then(() => { return pingData.id; });
|
||||
}
|
||||
}, error => this._log.error("testSavePing - Rejection", error));
|
||||
},
|
||||
|
||||
finishPingRequest: function finishPingRequest(success, startTime, ping, isPersisted) {
|
||||
this._log.trace("finishPingRequest - Success " + success + ", Persisted " + isPersisted);
|
||||
|
||||
let hping = Telemetry.getHistogramById("TELEMETRY_PING");
|
||||
let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
|
||||
|
||||
hsuccess.add(success);
|
||||
hping.add(new Date() - startTime);
|
||||
|
||||
if (success) {
|
||||
if (success && isPersisted) {
|
||||
return TelemetryFile.cleanupPingFile(ping);
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
|
@ -208,23 +521,44 @@ let Impl = {
|
|||
},
|
||||
|
||||
submissionPath: function submissionPath(ping) {
|
||||
let slug;
|
||||
if (!ping) {
|
||||
slug = this._uuid;
|
||||
// The new ping format contains an "application" section, the old one doesn't.
|
||||
let pathComponents;
|
||||
if (isNewPingFormat(ping)) {
|
||||
// We insert the Ping id in the URL to simplify server handling of duplicated
|
||||
// pings.
|
||||
let app = ping.application;
|
||||
pathComponents = [
|
||||
ping.id, ping.type, app.name, app.version, app.channel, app.buildId
|
||||
];
|
||||
} else {
|
||||
let info = ping.payload.info;
|
||||
let pathComponents = [ping.slug, info.reason, info.appName,
|
||||
info.appVersion, info.appUpdateChannel,
|
||||
info.appBuildID];
|
||||
slug = pathComponents.join("/");
|
||||
// This is a ping in the old format.
|
||||
if (!("slug" in ping)) {
|
||||
// That's odd, we don't have a slug. Generate one so that TelemetryFile.jsm works.
|
||||
ping.slug = generateUUID();
|
||||
}
|
||||
|
||||
// Do we have enough info to build a submission URL?
|
||||
let payload = ("payload" in ping) ? ping.payload : null;
|
||||
if (payload && ("info" in payload)) {
|
||||
let info = ping.payload.info;
|
||||
pathComponents = [ ping.slug, info.reason, info.appName, info.appVersion,
|
||||
info.appUpdateChannel, info.appBuildID ];
|
||||
} else {
|
||||
// Only use the UUID as the slug.
|
||||
pathComponents = [ ping.slug ];
|
||||
}
|
||||
}
|
||||
|
||||
let slug = pathComponents.join("/");
|
||||
return "/submit/telemetry/" + slug;
|
||||
},
|
||||
|
||||
doPing: function doPing(server, ping) {
|
||||
this._log.trace("doPing - Server " + server);
|
||||
doPing: function doPing(ping, isPersisted) {
|
||||
this._log.trace("doPing - Server " + this._server + ", Persisted " + isPersisted);
|
||||
let deferred = Promise.defer();
|
||||
let url = server + this.submissionPath(ping);
|
||||
let isNewPing = isNewPingFormat(ping);
|
||||
let version = isNewPing ? PING_FORMAT_VERSION : 1;
|
||||
let url = this._server + this.submissionPath(ping) + "?v=" + version;
|
||||
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
||||
.createInstance(Ci.nsIXMLHttpRequest);
|
||||
request.mozBackgroundRequest = true;
|
||||
|
@ -235,24 +569,34 @@ let Impl = {
|
|||
let startTime = new Date();
|
||||
|
||||
function handler(success) {
|
||||
let handleCompletion = event => {
|
||||
if (success) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject(event);
|
||||
}
|
||||
};
|
||||
|
||||
return function(event) {
|
||||
this.finishPingRequest(success, startTime, ping).then(() => {
|
||||
if (success) {
|
||||
deferred.resolve();
|
||||
} else {
|
||||
deferred.reject(event);
|
||||
}
|
||||
});
|
||||
this.finishPingRequest(success, startTime, ping, isPersisted)
|
||||
.then(() => handleCompletion(event),
|
||||
error => {
|
||||
this._log.error("doPing - Request Success " + success + ", Error " +
|
||||
error);
|
||||
handleCompletion(event);
|
||||
});
|
||||
};
|
||||
}
|
||||
request.addEventListener("error", handler(false).bind(this), false);
|
||||
request.addEventListener("load", handler(true).bind(this), false);
|
||||
|
||||
// If that's a legacy ping format, just send its payload.
|
||||
let networkPayload = isNewPing ? ping : ping.payload;
|
||||
request.setRequestHeader("Content-Encoding", "gzip");
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(ping.payload));
|
||||
let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(networkPayload));
|
||||
utf8Payload += converter.Finish();
|
||||
let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
|
@ -316,14 +660,32 @@ let Impl = {
|
|||
|
||||
/**
|
||||
* Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry.
|
||||
*
|
||||
* This delayed initialization means TelemetryPing init can be in the following states:
|
||||
* 1) setupTelemetry was never called
|
||||
* or it was called and
|
||||
* 2) _delayedInitTask was scheduled, but didn't run yet.
|
||||
* 3) _delayedInitTask is currently running.
|
||||
* 4) _delayedInitTask finished running and is nulled out.
|
||||
*/
|
||||
setupTelemetry: function setupTelemetry(testing) {
|
||||
this._initStarted = true;
|
||||
if (testing && !this._log) {
|
||||
this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
|
||||
}
|
||||
|
||||
this._log.trace("setupTelemetry");
|
||||
|
||||
if (this._delayedInitTask) {
|
||||
this._log.error("setupTelemetry - init task already running");
|
||||
return this._delayedInitTaskDeferred.promise;
|
||||
}
|
||||
|
||||
if (this._initialized && !testing) {
|
||||
this._log.error("setupTelemetry - already initialized");
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Initialize some probes that are kept in their own modules
|
||||
this._thirdPartyCookies = new ThirdPartyCookieProbe();
|
||||
this._thirdPartyCookies.init();
|
||||
|
@ -342,41 +704,91 @@ let Impl = {
|
|||
// Delay full telemetry initialization to give the browser time to
|
||||
// run various late initializers. Otherwise our gathered memory
|
||||
// footprint and other numbers would be too optimistic.
|
||||
let deferred = Promise.defer();
|
||||
let delayedTask = new DeferredTask(function* () {
|
||||
this._initialized = true;
|
||||
this._delayedInitTaskDeferred = Promise.defer();
|
||||
this._delayedInitTask = new DeferredTask(function* () {
|
||||
try {
|
||||
this._initialized = true;
|
||||
|
||||
yield TelemetryFile.loadSavedPings();
|
||||
// If we have any TelemetryPings lying around, we'll be aggressive
|
||||
// and try to send them all off ASAP.
|
||||
if (TelemetryFile.pingsOverdue > 0) {
|
||||
this._log.trace("setupChromeProcess - Sending " + TelemetryFile.pingsOverdue +
|
||||
" overdue pings now.");
|
||||
// It doesn't really matter what we pass to this.send as a reason,
|
||||
// since it's never sent to the server. All that this.send does with
|
||||
// the reason is check to make sure it's not a test-ping.
|
||||
yield this.send("overdue-flush");
|
||||
yield TelemetryEnvironment.init();
|
||||
|
||||
yield TelemetryFile.loadSavedPings();
|
||||
// If we have any TelemetryPings lying around, we'll be aggressive
|
||||
// and try to send them all off ASAP.
|
||||
if (TelemetryFile.pingsOverdue > 0) {
|
||||
this._log.trace("setupChromeProcess - Sending " + TelemetryFile.pingsOverdue +
|
||||
" overdue pings now.");
|
||||
// It doesn't really matter what we pass to this.send as a reason,
|
||||
// since it's never sent to the server. All that this.send does with
|
||||
// the reason is check to make sure it's not a test-ping.
|
||||
yield this.sendPersistedPings();
|
||||
}
|
||||
|
||||
if ("@mozilla.org/datareporting/service;1" in Cc) {
|
||||
let drs = Cc["@mozilla.org/datareporting/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
this._clientID = yield drs.getClientID();
|
||||
// Update cached client id.
|
||||
Preferences.set(PREF_CACHED_CLIENTID, this._clientID);
|
||||
} else {
|
||||
// Nuke potentially cached client id.
|
||||
Preferences.reset(PREF_CACHED_CLIENTID);
|
||||
}
|
||||
|
||||
Telemetry.asyncFetchTelemetryData(function () {});
|
||||
this._delayedInitTaskDeferred.resolve();
|
||||
} catch (e) {
|
||||
this._delayedInitTaskDeferred.reject(e);
|
||||
} finally {
|
||||
this._delayedInitTask = null;
|
||||
this._delayedInitTaskDeferred = null;
|
||||
}
|
||||
|
||||
if ("@mozilla.org/datareporting/service;1" in Cc) {
|
||||
let drs = Cc["@mozilla.org/datareporting/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
this._clientID = yield drs.getClientID();
|
||||
// Update cached client id.
|
||||
Preferences.set(PREF_CACHED_CLIENTID, this._clientID);
|
||||
} else {
|
||||
// Nuke potentially cached client id.
|
||||
Preferences.reset(PREF_CACHED_CLIENTID);
|
||||
}
|
||||
|
||||
Telemetry.asyncFetchTelemetryData(function () {});
|
||||
deferred.resolve();
|
||||
|
||||
}.bind(this), testing ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY);
|
||||
|
||||
delayedTask.arm();
|
||||
return deferred.promise;
|
||||
AsyncShutdown.sendTelemetry.addBlocker("TelemetryPing: shutting down",
|
||||
() => this.shutdown(),
|
||||
() => this._getState());
|
||||
|
||||
this._delayedInitTask.arm();
|
||||
return this._delayedInitTaskDeferred.promise;
|
||||
},
|
||||
|
||||
shutdown: function() {
|
||||
this._log.trace("shutdown");
|
||||
|
||||
let cleanup = () => {
|
||||
if (!this._initialized) {
|
||||
return;
|
||||
}
|
||||
let reset = () => {
|
||||
this._initialized = false;
|
||||
this._initStarted = false;
|
||||
};
|
||||
return this._shutdownBarrier.wait().then(
|
||||
() => TelemetryEnvironment.shutdown().then(reset, reset));
|
||||
};
|
||||
|
||||
// We can be in one the following states here:
|
||||
// 1) setupTelemetry was never called
|
||||
// or it was called and
|
||||
// 2) _delayedInitTask was scheduled, but didn't run yet.
|
||||
// 3) _delayedInitTask is running now.
|
||||
// 4) _delayedInitTask finished running already.
|
||||
|
||||
// This handles 1).
|
||||
if (!this._initStarted) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// This handles 4).
|
||||
if (!this._delayedInitTask) {
|
||||
// We already ran the delayed initialization.
|
||||
return cleanup();
|
||||
}
|
||||
|
||||
// This handles 2) and 3).
|
||||
this._delayedInitTask.disarm();
|
||||
return this._delayedInitTask.finalize().then(cleanup);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -412,4 +824,15 @@ let Impl = {
|
|||
get clientID() {
|
||||
return this._clientID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an object describing the current state of this module for AsyncShutdown diagnostics.
|
||||
*/
|
||||
_getState: function() {
|
||||
return {
|
||||
initialized: this._initialized,
|
||||
initStarted: this._initStarted,
|
||||
haveDelayedInitTask: !!this._delayedInitTask,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
@ -17,10 +17,10 @@ Finally, the structure also contains the `payload`, which is the specific data s
|
|||
Structure::
|
||||
|
||||
{
|
||||
type: <string>, // "main", "activation", "deletion", ...
|
||||
type: <string>, // "main", "activation", "deletion", "saved-session", ...
|
||||
id: <UUID>, // a UUID that identifies this ping
|
||||
creationDate: <ISO date>, // the date the ping was generated
|
||||
version: <number>, // the version of the ping format, currently 2
|
||||
version: <number>, // the version of the ping format, currently 4
|
||||
|
||||
application: {
|
||||
architecture: <string>, // build architecture, e.g. x86
|
||||
|
|
|
@ -14,6 +14,7 @@ Structure::
|
|||
{
|
||||
build: {
|
||||
applicationId: <string>, // nsIXULAppInfo.ID
|
||||
applicationName: <string>, // "Firefox"
|
||||
architecture: <string>, // e.g. "x86", build architecture for the active build
|
||||
architecturesInBinary: <string>, // e.g. "i386-x86_64", from nsIMacUtils.architecturesInBinary, only present for mac universal builds
|
||||
buildId: <string>, // e.g. "20141126041045"
|
||||
|
@ -24,22 +25,22 @@ Structure::
|
|||
hotfixVersion: <string>, // e.g. "20141211.01"
|
||||
},
|
||||
settings: {
|
||||
blocklistEnabled: <bool>, // false on failure
|
||||
isDefaultBrowser: <bool>, // null on failure
|
||||
blocklistEnabled: <bool>, // true on failure
|
||||
isDefaultBrowser: <bool>, // null on failure, not available on Android
|
||||
e10sEnabled: <bool>, // false on failure
|
||||
telemetryEnabled: <bool>, // false on failure
|
||||
locale: <string>, // e.g. "it", null on failure
|
||||
update: {
|
||||
channel: <string>, // e.g. "release", null on failure
|
||||
enabled: <bool>, // false on failure
|
||||
autoDownload: <bool>, // false on failure
|
||||
enabled: <bool>, // true on failure
|
||||
autoDownload: <bool>, // true on failure
|
||||
},
|
||||
userPrefs: {
|
||||
// Two possible behaviours: values of the whitelisted prefs, or for some prefs we
|
||||
// only record they are present with value being set to null.
|
||||
},
|
||||
},
|
||||
profile: {
|
||||
profile: { // This section is not available on Android.
|
||||
creationDate: <integer>, // integer days since UNIX epoch, e.g. 16446
|
||||
resetDate: <integer>, // integer days since UNIX epoch, e.g. 16446 - optional
|
||||
},
|
||||
|
@ -86,16 +87,16 @@ Structure::
|
|||
},
|
||||
hdd: {
|
||||
profile: { // hdd where the profile folder is located
|
||||
model: <string>, // null on failure
|
||||
revision: <string>, // null on failure
|
||||
model: <string>, // windows only or null on failure
|
||||
revision: <string>, // windows only or null on failure
|
||||
},
|
||||
binary: { // hdd where the application binary is located
|
||||
model: <string>, // null on failure
|
||||
revision: <string>, // null on failure
|
||||
model: <string>, // windows only or null on failure
|
||||
revision: <string>, // windows only or null on failure
|
||||
},
|
||||
system: { // hdd where the system files are located
|
||||
model: <string>, // null on failure
|
||||
revision: <string>, // null on failure
|
||||
model: <string>, // windows only or null on failure
|
||||
revision: <string>, // windows only or null on failure
|
||||
},
|
||||
},
|
||||
gfx: {
|
||||
|
@ -122,7 +123,7 @@ Structure::
|
|||
activeAddons: { // the currently enabled addons
|
||||
<addon id>: {
|
||||
blocklisted: <bool>,
|
||||
description: <string>,
|
||||
description: <string>, // null if not available
|
||||
name: <string>,
|
||||
userDisabled: <bool>,
|
||||
appDisabled: <bool>,
|
||||
|
|