Merge fx-team to m-c. a=merge

This commit is contained in:
Ryan VanderMeulen 2016-01-13 20:20:20 -05:00
commit a9bba531ba
61 changed files with 1014 additions and 773 deletions

View File

@ -8,7 +8,7 @@
<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
<?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/controlcenter/panel.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/customizableui/panelUIOverlay.css" type="text/css"?>
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>

View File

@ -189,7 +189,7 @@ ident:
merge-%:
ifdef LOCALE_MERGEDIR
$(RM) -rf $(LOCALE_MERGEDIR)
MACOSX_DEPLOYMENT_TARGET= compare-locales -m $(LOCALE_MERGEDIR) $(srcdir)/l10n.ini $(L10NBASEDIR) $*
$(topsrcdir)/mach compare-locales --merge-dir $(LOCALE_MERGEDIR) $*
endif
@echo

View File

@ -12,7 +12,7 @@ clientSuperShortname=Hello
## LOCALIZATION_NOTE(loopMenuItem_label): Label of the menu item that is placed
## inside the browser 'Tools' menu. Use the unicode ellipsis char, \u2026, or
## use "..." if \u2026 doesn't suit traditions in your locale.
loopMenuItem_label=Start a conversation…
loopMenuItem_label=Start a Conversation…
loopMenuItem_accesskey=t
## LOCALIZATION_NOTE(sign_in_again_title_line_one, sign_in_again_title_line_two2):

View File

@ -80,6 +80,10 @@ function reload(event) {
// Ask the loader to update itself and reopen the toolbox if needed
const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
devtools.reload(reloadToolbox);
// Also tells gDevTools to reload its dependencies
const {gDevTools} = Cu.import("resource://devtools/client/framework/gDevTools.jsm", {});
gDevTools.reload();
}
let listener;

View File

@ -326,6 +326,8 @@ skip-if = e10s && debug
skip-if = e10s && debug
[browser_dbg_parser-10.js]
skip-if = e10s && debug
[browser_dbg_parser-11.js]
skip-if = e10s && debug
[browser_dbg_pause-exceptions-01.js]
skip-if = e10s && debug
[browser_dbg_pause-exceptions-02.js]

View File

@ -0,0 +1,39 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Checks if self-closing <script/> tags are parsed by Parser.jsm
*/
function test() {
let { Parser } = Cu.import("resource://devtools/shared/Parser.jsm", {});
let source = [
'<script type="text/javascript" src="chrome://foo.js"/>',
'<script type="application/javascript;version=1.8" src="chrome://baz.js"/>',
'<script async defer src="chrome://foobar.js"/>',
'<script type="application/javascript"/>"hello third"',
'<script type="application/javascript">"hello fourth"</script>',
].join("\n");
let parser = new Parser();
let parsed = parser.get(source);
is(parser.errors.length, 0,
"There should be no errors logged when parsing.");
is(parsed.scriptCount, 5,
"There should be 5 scripts parsed in the parent HTML source.");
is(parsed.getScriptInfo(source.indexOf("foo.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})",
"the first script is empty");
is(parsed.getScriptInfo(source.indexOf("baz.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})",
"the second script is empty");
is(parsed.getScriptInfo(source.indexOf("foobar.js\"/>") + 1).toSource(), "({start:-1, length:-1, index:-1})",
"the third script is empty");
is(parsed.getScriptInfo(source.indexOf("hello third!")).toSource(), "({start:-1, length:-1, index:-1})",
"Inline script on self-closing tag not considered a script");
is(parsed.getScriptInfo(source.indexOf("hello fourth")).toSource(), "({start:267, length:14, index:4})",
"The fourth script was located correctly.");
finish();
}

View File

@ -9,7 +9,10 @@
<?xml-stylesheet href="chrome://devtools/skin/common.css"?>
<?xml-stylesheet href="chrome://devtools/skin/eyedropper.css" type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
no-theme="true">
<script type="application/javascript;version=1.8"
src="chrome://devtools/content/shared/theme-switching.js"/>
<commandset id="eyedropper-commandset">
<command id="eyedropper-cmd-close"
oncommand="void(0);"/>

View File

@ -10,8 +10,17 @@ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const { require, loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
const promise = require("promise");
// Make most dependencies be reloadable so that the reload addon
// can update all of them while keeping gDevTools.jsm as-is
// Bug 1188405 is going to refactor this JSM into a commonjs module
// so that it can be reloaded as other modules.
let require, loader, promise, DefaultTools, DefaultThemes;
let loadDependencies = () => {
let l = Cu.import("resource://devtools/shared/Loader.jsm", {});
require = l.require;
loader = l.loader;
promise = require("promise");
// Load target and toolbox lazily as they need gDevTools to be fully initialized
loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
@ -23,8 +32,12 @@ XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
const {defaultTools: DefaultTools, defaultThemes: DefaultThemes} =
require("devtools/client/definitions");
let d = require("devtools/client/definitions");
DefaultTools = d.defaultTools;
DefaultThemes = d.defaultThemes;
};
loadDependencies();
const EventEmitter = require("devtools/shared/event-emitter");
const Telemetry = require("devtools/client/shared/telemetry");
const {JsonView} = require("devtools/client/jsonview/main");
@ -511,6 +524,11 @@ DevTools.prototype = {
// Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
},
// Force reloading dependencies if the loader happens to have reloaded
reload() {
loadDependencies();
},
/**
* Iterator that yields each of the toolboxes.
*/

View File

@ -356,7 +356,7 @@ Toolbox.prototype = {
iframe.setAttribute("src", this._URL);
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
let domHelper = new DOMHelpers(iframe.contentWindow);
domHelper.onceDOMReady(() => domReady.resolve());
domHelper.onceDOMReady(() => domReady.resolve(), this._URL);
// Optimization: fire up a few other things before waiting on
// the iframe being ready (makes startup faster)
@ -365,7 +365,6 @@ Toolbox.prototype = {
// Attach the thread
this._threadClient = yield attachThread(this);
yield domReady.promise;
this.isReady = true;

View File

@ -1,51 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.dim > #root,
.font:not(.has-code) .font-css-code,
.font-is-local,
.font-is-remote,
.font.is-local .font-format-url,
#template {
display: none;
}
.font.is-remote .font-is-remote,
.font.is-local .font-is-local {
display: inline;
}
.font-format::before {
content: "(";
}
.font-format::after {
content: ")";
}
.preview-input-toolbar {
display: flex;
width: 100%;
}
.font-preview-container {
overflow-x: auto;
}
#preview-text-input {
font: inherit;
margin-top: 1px;
margin-bottom: 1px;
padding-top: 0;
padding-bottom: 0;
flex: 1;
}
:root {
height: 100%;
}
#root {
overflow: auto;
}

View File

@ -11,7 +11,6 @@
<head>
<title>&title;</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<link rel="stylesheet" href="fonts.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/fonts.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>

View File

@ -1,118 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* {
box-sizing: border-box;
}
:root {
height: 100%;
}
body {
margin: 0;
display: flex;
flex-direction: column;
height: 100%;
}
#ruleview-container {
-moz-user-select: text;
overflow: auto;
min-height: 0;
flex: 1;
}
#ruleview-container.non-interactive {
pointer-events: none;
visibility: collapse;
transition: visibility 0.25s;
}
.devtools-toolbar {
width: 100%;
display: flex;
}
#pseudo-class-panel {
position: relative;
margin-top: -1px;
margin-bottom: -1px;
overflow-y: hidden;
max-height: 24px;
outline: 0 !important;
transition-property: max-height;
transition-duration: 150ms;
transition-timing-function: ease;
}
#pseudo-class-panel[hidden] {
max-height: 0px;
}
#pseudo-class-panel > label {
-moz-user-select: none;
flex-grow: 1;
display: flex;
align-items: center;
}
.ruleview {
overflow: auto;
-moz-user-select: text;
}
.ruleview-code {
direction: ltr;
}
.ruleview-property:not(:hover) > .ruleview-enableproperty {
pointer-events: none;
}
.ruleview-namecontainer {
cursor: text;
}
.ruleview-propertyvaluecontainer {
cursor: text;
padding-right: 5px;
}
.ruleview-propertyvaluecontainer a {
cursor: pointer;
}
.ruleview-computedlist,
.ruleview-overridden-rule-filter[hidden],
.ruleview-warning[hidden] {
display: none;
}
.ruleview-computedlist[user-open],
.ruleview-computedlist[filter-open] {
display: block;
}
.ruleview-expandable-container {
display: none;
}
.show-expandable-container + .ruleview-expandable-container {
display: block;
}
.ruleview .ruleview-expander {
vertical-align: middle;
}
.ruleview-header {
vertical-align: middle;
min-height: 1.5em;
line-height: 1.5em;
}
.ruleview-header.ruleview-expandable-header {
cursor: pointer;
}

View File

@ -17,7 +17,6 @@
<title>&ruleViewTitle;</title>
<link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/common.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/content/inspector/rules/rules.css" type="text/css"/>
<link rel="stylesheet" href="chrome://devtools/skin/rules.css" type="text/css"/>
<script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
<script type="application/javascript;version=1.8">
@ -52,7 +51,7 @@
</div>
</div>
<div id="ruleview-container" class="ruleview devtools-monospace">
<div id="ruleview-container" class="devtools-monospace">
</div>
</body>
</html>

View File

@ -27,14 +27,12 @@ devtools.jar:
content/styleeditor/styleeditor.css (styleeditor/styleeditor.css)
content/storage/storage.xul (storage/storage.xul)
content/inspector/computed/computed.xhtml (inspector/computed/computed.xhtml)
content/inspector/fonts/fonts.css (inspector/fonts/fonts.css)
content/inspector/fonts/fonts.js (inspector/fonts/fonts.js)
content/inspector/fonts/fonts.xhtml (inspector/fonts/fonts.xhtml)
content/inspector/layout/layout.js (inspector/layout/layout.js)
content/inspector/layout/layout.xhtml (inspector/layout/layout.xhtml)
content/inspector/markup/markup.css (inspector/markup/markup.css)
content/inspector/markup/markup.xhtml (inspector/markup/markup.xhtml)
content/inspector/rules/rules.css (inspector/rules/rules.css)
content/inspector/rules/rules.xhtml (inspector/rules/rules.xhtml)
content/animationinspector/animation-controller.js (animationinspector/animation-controller.js)
content/animationinspector/animation-panel.js (animationinspector/animation-panel.js)
@ -148,7 +146,9 @@ devtools.jar:
content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
content/aboutdebugging/aboutdebugging.js (aboutdebugging/aboutdebugging.js)
% skin devtools classic/1.0 %skin/
* skin/common.css (themes/common.css)
skin/devtools-browser.css (themes/devtools-browser.css)
skin/common.css (themes/common.css)
skin/splitters.css (themes/splitters.css)
skin/dark-theme.css (themes/dark-theme.css)
skin/light-theme.css (themes/light-theme.css)
skin/toolbars.css (themes/toolbars.css)

View File

@ -11,11 +11,6 @@
<!ENTITY window.title "Web Console">
<!ENTITY browserConsole.title "Browser Console">
<!-- LOCALIZATION NOTE (saveBodies.label): You can see this string in the Web
- Console context menu. -->
<!ENTITY saveBodies.label "Log Request and Response Bodies">
<!ENTITY saveBodies.accesskey "L">
<!-- LOCALIZATION NOTE (openURL.label): You can see this string in the Web
- Console context menu. -->
<!ENTITY openURL.label "Open URL in New Tab">

View File

@ -73,11 +73,8 @@ var HarAutomation = Class({
this.tabClient = this.toolbox.target.activeTab;
this.webConsoleClient = this.toolbox.target.activeConsole;
let netPrefs = { "NetworkMonitor.saveRequestAndResponseBodies": true };
this.webConsoleClient.setPreferences(netPrefs, () => {
this.tabWatcher = new TabWatcher(this.toolbox, this);
this.tabWatcher.connect();
});
},
pageLoadBegin: function(aResponse) {

View File

@ -10,7 +10,6 @@ var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
const NET_STRINGS_URI = "chrome://devtools/locale/netmonitor.properties";
const PKI_STRINGS_URI = "chrome://pippki/locale/pippki.properties";
const LISTENERS = [ "NetworkActivity" ];
const NET_PREFS = { "NetworkMonitor.saveRequestAndResponseBodies": true };
// The panel's window global is an EventEmitter firing the following events:
const EVENTS = {
@ -223,13 +222,6 @@ var NetMonitorController = {
this.tabClient = this._target.activeTab;
}
let connectWebConsole = () => {
let deferred = promise.defer();
this.webConsoleClient = this._target.activeConsole;
this.webConsoleClient.setPreferences(NET_PREFS, deferred.resolve);
return deferred.promise;
};
let connectTimeline = () => {
// Don't start up waiting for timeline markers if the server isn't
// recent enough to emit the markers we're interested in.
@ -239,7 +231,7 @@ var NetMonitorController = {
}
};
yield connectWebConsole();
this.webConsoleClient = this._target.activeConsole;
yield connectTimeline();
this.TargetEventsHandler.connect();

View File

@ -74,6 +74,7 @@ skip-if = e10s # Bug 1091596
[browser_net_html-preview.js]
[browser_net_icon-preview.js]
[browser_net_image-tooltip.js]
skip-if = (os == "win" && os_version == "6.2" && bits == 64) # Bug 1234341
[browser_net_json-long.js]
[browser_net_json-malformed.js]
[browser_net_json_custom_mime.js]

View File

@ -137,7 +137,7 @@ DOMHelpers.prototype = {
* chrome iframes are loaded in content docshells (in Firefox
* tabs for example).
*/
onceDOMReady: function Helpers_onLocationChange(callback) {
onceDOMReady: function Helpers_onLocationChange(callback, targetURL) {
let window = this.window;
let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@ -151,6 +151,12 @@ DOMHelpers.prototype = {
Services.tm.mainThread.dispatch(callback, 0);
}
}
if ((window.document.readyState == "complete" ||
window.document.readyState == "interactive") &&
window.location.href == targetURL) {
Services.tm.mainThread.dispatch(callback, 0);
} else {
docShell.chromeEventHandler.addEventListener("DOMContentLoaded", onReady, false);
}
}
};

View File

@ -82,6 +82,9 @@ exports.getTestActorWithoutToolbox = Task.async(function* (tab) {
client.connect(deferred.resolve);
yield deferred.promise;
// We also need to make sure the test actor is registered on the server.
yield registerTestActor(client);
return getTestActor(client, tab);
});

View File

@ -5,6 +5,25 @@
(function() {
const SCROLLBARS_URL = "chrome://devtools/skin/floating-scrollbars-light.css";
let documentElement = document.documentElement;
let os;
let platform = navigator.platform;
if (platform.startsWith("Win")) {
os = "win";
} else if (platform.startsWith("Mac")) {
os = "mac";
} else {
os = "linux";
}
documentElement.setAttribute("platform", os);
// no-theme attributes allows to just est the platform attribute
// to have per-platform CSS working correctly.
if (documentElement.getAttribute("no-theme") === "true") {
return;
}
let devtoolsStyleSheets = new WeakMap();
function forceStyle() {
@ -144,17 +163,6 @@
const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
const StylesheetUtils = require("sdk/stylesheet/utils");
let os;
let platform = navigator.platform;
if (platform.startsWith("Win")) {
os = "win";
} else if (platform.startsWith("Mac")) {
os = "mac";
} else {
os = "linux";
}
documentElement.setAttribute("platform", os);
if (documentElement.hasAttribute("force-theme")) {
switchTheme(documentElement.getAttribute("force-theme"));
} else {

View File

@ -1,66 +1,33 @@
%if 0
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
%endif
@import url("splitters.css");
:root {
font: message-box;
%ifdef XP_MACOSX
}
:root[platform="mac"] {
--monospace-font-family: Menlo, monospace;
%elifdef XP_WIN
}
:root[platform="win"] {
--monospace-font-family: Consolas, monospace;
%else
}
:root[platform="linux"] {
--monospace-font-family: monospace;
%endif
}
.devtools-monospace {
font-family: var(--monospace-font-family);
%if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
}
:root[platform="linux"] .devtools-monospace {
font-size: 80%;
%endif
}
/* Bottom-docked toolbox minimize transition */
.devtools-toolbox-bottom-iframe {
transition: margin-bottom .1s;
}
/* Splitters */
.devtools-horizontal-splitter {
-moz-appearance: none;
background-image: none;
background-color: transparent;
border: 0;
border-bottom: 1px solid rgba(118, 121, 125, .5);
min-height: 3px;
height: 3px;
margin-top: -3px;
position: relative;
}
.devtools-side-splitter {
-moz-appearance: none;
background-image: none;
background-color: transparent;
border: 0;
-moz-border-end: 1px solid rgba(118, 121, 125, .5);
min-width: 3px;
width: 3px;
-moz-margin-start: -3px;
position: relative;
cursor: e-resize;
}
.devtools-horizontal-splitter.disabled,
.devtools-side-splitter.disabled {
pointer-events: none;
}
.devtools-toolbox-side-iframe {
min-width: 465px;
}
/* Autocomplete Popup */
/* Dark and light theme */
@ -71,11 +38,11 @@
background-color: transparent;
border-radius: 3px;
overflow-x: hidden;
%if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
max-height: 32rem;
%else
max-height: 40rem;
%endif
}
:root[platform="linux"] .devtools-autocomplete-popup {
max-height: 32rem;
}
.devtools-autocomplete-listbox {
@ -248,17 +215,6 @@
background: transparent;
}
/* Eyedropper Widget */
.devtools-eyedropper-panel {
pointer-events: none;
-moz-appearance: none;
width: 156px;
height: 120px;
background-color: transparent;
border: none;
}
/* links to source code, like displaying `myfile.js:45` */
.devtools-source-link {

View File

@ -0,0 +1,27 @@
/* 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/. */
@import url("splitters.css");
/* Bottom-docked toolbox minimize transition */
.devtools-toolbox-bottom-iframe {
transition: margin-bottom .1s;
}
.devtools-toolbox-side-iframe {
min-width: 465px;
}
/* Eyedropper Widget */
/* <panel> added to mainPopupSet */
.devtools-eyedropper-panel {
pointer-events: none;
-moz-appearance: none;
width: 156px;
height: 120px;
background-color: transparent;
border: none;
}

View File

@ -1,7 +1,15 @@
/* 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/. */
* {
box-sizing: border-box;
}
:root {
height: 100%;
}
body {
display: flex;
flex-direction: column;
@ -10,6 +18,10 @@ body {
padding-bottom: 20px;
}
#root {
overflow: auto;
}
#all-fonts {
padding: 0;
margin: 0;
@ -25,6 +37,46 @@ body {
right: 0;
}
.dim > #root,
.font:not(.has-code) .font-css-code,
.font-is-local,
.font-is-remote,
.font.is-local .font-format-url,
#template {
display: none;
}
.font.is-remote .font-is-remote,
.font.is-local .font-is-local {
display: inline;
}
.font-format::before {
content: "(";
}
.font-format::after {
content: ")";
}
.preview-input-toolbar {
display: flex;
width: 100%;
}
.font-preview-container {
overflow-x: auto;
}
#preview-text-input {
font: inherit;
margin-top: 1px;
margin-bottom: 1px;
padding-top: 0;
padding-bottom: 0;
flex: 1;
}
.font {
padding: 10px 10px;
}

View File

@ -72,6 +72,14 @@
cursor: pointer;
}
.event-header:first-child {
border-width: 0;
}
.event-header:not(:first-child) {
border-width: 1px 0 0 0;
}
.event-tooltip-event-type,
.event-tooltip-filename,
.event-tooltip-attributes {
@ -105,8 +113,15 @@
.event-tooltip-content-box {
display: none;
overflow: auto;
height: 100px;
overflow: hidden;
-moz-margin-end: 0;
border: 1px solid var(--theme-splitter-color);
border-width: 1px 0 0 0;
}
.event-toolbox-content-box iframe {
height: 100%;
}
.event-tooltip-content-box[open] {

View File

@ -13,10 +13,103 @@
--rule-filter-icon: url(images/magnifying-glass.png);
}
.ruleview {
* {
box-sizing: border-box;
}
:root {
height: 100%;
}
body {
margin: 0;
display: flex;
flex-direction: column;
height: 100%;
}
#ruleview-container {
-moz-user-select: text;
overflow: auto;
min-height: 0;
flex: 1;
height: 100%;
}
#ruleview-container.non-interactive {
pointer-events: none;
visibility: collapse;
transition: visibility 0.25s;
}
.devtools-toolbar {
width: 100%;
display: flex;
}
#pseudo-class-panel {
position: relative;
margin-top: -1px;
margin-bottom: -1px;
overflow-y: hidden;
max-height: 24px;
outline: 0 !important;
transition-property: max-height;
transition-duration: 150ms;
transition-timing-function: ease;
}
#pseudo-class-panel[hidden] {
max-height: 0px;
}
#pseudo-class-panel > label {
-moz-user-select: none;
flex-grow: 1;
display: flex;
align-items: center;
}
.ruleview-code {
direction: ltr;
}
.ruleview-property:not(:hover) > .ruleview-enableproperty {
pointer-events: none;
}
.ruleview-expandable-container {
display: none;
}
.show-expandable-container + .ruleview-expandable-container {
display: block;
}
.ruleview-namecontainer {
cursor: text;
}
.ruleview-propertyvaluecontainer {
cursor: text;
padding-right: 5px;
}
.ruleview-propertyvaluecontainer a {
cursor: pointer;
}
.ruleview-computedlist,
.ruleview-overridden-rule-filter[hidden],
.ruleview-warning[hidden] {
display: none;
}
.ruleview-computedlist[user-open],
.ruleview-computedlist[filter-open] {
display: block;
}
.ruleview-rule-source {
text-align: end;
float: right;
@ -46,6 +139,9 @@
padding: 1px 4px;
-moz-user-select: none;
word-wrap: break-word;
vertical-align: middle;
min-height: 1.5em;
line-height: 1.5em;
}
:root[platform="win"] .ruleview-header,
@ -53,6 +149,10 @@
margin-top: 4px;
}
.ruleview-header.ruleview-expandable-header {
cursor: pointer;
}
.ruleview-rule-pseudo-element {
padding-left:20px;
border-left: solid 10px;
@ -148,6 +248,7 @@
}
.ruleview-expander {
vertical-align: middle;
display: inline-block;
}

View File

@ -0,0 +1,34 @@
/* 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/. */
/* Splitters */
.devtools-horizontal-splitter {
-moz-appearance: none;
background-image: none;
background-color: transparent;
border: 0;
border-bottom: 1px solid rgba(118, 121, 125, .5);
min-height: 3px;
height: 3px;
margin-top: -3px;
position: relative;
}
.devtools-side-splitter {
-moz-appearance: none;
background-image: none;
background-color: transparent;
border: 0;
-moz-border-end: 1px solid rgba(118, 121, 125, .5);
min-width: 3px;
width: 3px;
-moz-margin-start: -3px;
position: relative;
cursor: e-resize;
}
.devtools-horizontal-splitter.disabled,
.devtools-side-splitter.disabled {
pointer-events: none;
}

View File

@ -171,6 +171,7 @@ skip-if = buildapp == 'mulet' || e10s # Bug 1042253 - webconsole e10s tests
[browser_console_log_inspectable_object.js]
[browser_console_native_getters.js]
[browser_console_navigation_marker.js]
[browser_console_netlogging.js]
[browser_console_nsiconsolemessage.js]
skip-if = buildapp == 'mulet'
[browser_console_optimized_out_vars.js]
@ -300,7 +301,6 @@ skip-if = os != "mac"
[browser_webconsole_bug_817834_add_edited_input_to_history.js]
[browser_webconsole_bug_837351_securityerrors.js]
skip-if = buildapp == 'mulet'
[browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js]
[browser_webconsole_filter_buttons_contextmenu.js]
[browser_webconsole_bug_1006027_message_timestamps_incorrect.js]
skip-if = e10s # Bug 1042253 - webconsole e10s tests (Linux debug intermittent)

View File

@ -0,0 +1,50 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/* 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/. */
// Tests that network log messages bring up the network panel.
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,Web Console network " +
"logging tests";
const TEST_NETWORK_REQUEST_URI =
"http://example.com/browser/devtools/client/webconsole/test/" +
"test-network-request.html";
var hud;
function test() {
loadTab(TEST_URI).then((tab) => {
HUDService.openBrowserConsoleOrFocus().then(aHud => {
hud = aHud;
HUDService.lastFinishedRequest.callback = testResponse;
BrowserTestUtils.loadURI(gBrowser.selectedBrowser,
TEST_NETWORK_REQUEST_URI);
});
});
}
function testResponse(request) {
hud.ui.webConsoleClient.getResponseContent(request.actor,
function(contentPacket) {
hud.ui.webConsoleClient.getRequestPostData(request.actor,
function(postDataPacket) {
// Check if page load was logged correctly.
ok(request, "Page load was logged");
is(request.request.url, TEST_NETWORK_REQUEST_URI,
"Logged network entry is page load");
is(request.request.method, "GET", "Method is correct");
ok(!postDataPacket.postData.text, "No request body was stored");
ok(postDataPacket.postDataDiscarded, "Request body was discarded");
ok(!contentPacket.content.text, "No response body was stored");
ok(contentPacket.contentDiscarded || request.fromCache,
"Response body was discarded or response came from the cache");
executeSoon(finishTest);
});
});
}

View File

@ -54,11 +54,6 @@ add_task(function* () {
let hud = yield openConsole();
yield hud.ui.setSaveRequestAndResponseBodies(true);
ok(hud.ui._saveRequestAndResponseBodies,
"The saveRequestAndResponseBodies property was successfully set.");
let gotLastRequest = waitForRequest();
let loaded = loadBrowser(browser);

View File

@ -33,10 +33,6 @@ function consoleOpened(hud) {
let deferred = promise.defer();
webConsoleClient = hud.ui.webConsoleClient;
hud.ui.setSaveRequestAndResponseBodies(true).then(() => {
ok(hud.ui._saveRequestAndResponseBodies,
"The saveRequestAndResponseBodies property was successfully set.");
HUDService.lastFinishedRequest.callback = (aHttpRequest) => {
let status = aHttpRequest.response.status;
lastFinishedRequests[status] = aHttpRequest;
@ -46,7 +42,6 @@ function consoleOpened(hud) {
}
};
content.location = TEST_URI2;
});
return deferred.promise;
}

View File

@ -1,124 +0,0 @@
/* vim:set ts=2 sw=2 sts=2 et: */
/*
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
// Tests that the 'Log Request and Response Bodies' buttons can be toggled
// with keyboard.
"use strict";
const TEST_URI = "data:text/html;charset=utf-8,Web Console test for " +
"bug 915141: Toggle log response bodies with keyboard";
var hud;
function test() {
let saveBodiesMenuItem;
let saveBodiesContextMenuItem;
loadTab(TEST_URI).then(({tab: tab}) => {
return openConsole(tab);
})
.then((aHud) => {
hud = aHud;
saveBodiesMenuItem = hud.ui.rootElement.querySelector("#saveBodies");
saveBodiesContextMenuItem = hud.ui.rootElement.querySelector("#saveBodiesContextMenu");
// Test the context menu action.
info("Testing 'Log Request and Response Bodies' menuitem of right click " +
"context menu.");
return openPopup(saveBodiesContextMenuItem);
})
.then(() => {
is(saveBodiesContextMenuItem.getAttribute("checked"), "false",
"Context menu: 'log responses' is not checked before action.");
is(hud.ui._saveRequestAndResponseBodies, false,
"Context menu: Responses are not logged before action.");
EventUtils.synthesizeKey("VK_DOWN", {});
EventUtils.synthesizeKey("VK_RETURN", {});
return waitForUpdate(saveBodiesContextMenuItem);
})
.then(() => {
is(saveBodiesContextMenuItem.getAttribute("checked"), "true",
"Context menu: 'log responses' is checked after menuitem was selected " +
"with keyboard.");
is(hud.ui._saveRequestAndResponseBodies, true,
"Context menu: Responses are saved after menuitem was selected with " +
"keyboard.");
return openPopup(saveBodiesMenuItem);
})
.then(() => {
// Test the 'Net' menu item.
info("Testing 'Log Request and Response Bodies' menuitem of 'Net' menu " +
"in the console.");
// 'Log Request and Response Bodies' should be selected due to previous
// test.
is(saveBodiesMenuItem.getAttribute("checked"), "true",
"Console net menu: 'log responses' is checked before action.");
is(hud.ui._saveRequestAndResponseBodies, true,
"Console net menu: Responses are logged before action.");
// The correct item is the last one in the menu.
EventUtils.synthesizeKey("VK_UP", {});
EventUtils.synthesizeKey("VK_RETURN", {});
return waitForUpdate(saveBodiesMenuItem);
})
.then(() => {
is(saveBodiesMenuItem.getAttribute("checked"), "false",
"Console net menu: 'log responses' is NOT checked after menuitem was " +
"selected with keyboard.");
is(hud.ui._saveRequestAndResponseBodies, false,
"Responses are NOT saved after menuitem was selected with keyboard.");
hud = null;
})
.then(finishTest);
}
/**
* Opens and waits for the menu containing menuItem to open.
* @param menuItem MenuItem
* A MenuItem in a menu that should be opened.
* @return A promise that's resolved once menu is open.
*/
function openPopup(menuItem) {
let menu = menuItem.parentNode;
let menuOpened = promise.defer();
let uiUpdated = promise.defer();
// The checkbox menuitem is updated asynchronously on 'popupshowing' event so
// it's better to wait for both the update to happen and the menu to open
// before continuing or the test might fail due to a race between menu being
// shown and the item updated to have the correct state.
hud.ui.once("save-bodies-ui-toggled", uiUpdated.resolve);
menu.addEventListener("popupshown", function onPopup() {
menu.removeEventListener("popupshown", onPopup);
menuOpened.resolve();
});
menu.openPopup();
return Promise.all([menuOpened.promise, uiUpdated.promise]);
}
/**
* Waits for the settings and menu containing menuItem to update.
* @param menuItem MenuItem
* The menuitem that should be updated.
* @return A promise that's resolved once the settings and menus are updated.
*/
function waitForUpdate(menuItem) {
info("Waiting for settings update to complete.");
let deferred = promise.defer();
hud.ui.once("save-bodies-pref-reversed", function() {
hud.ui.once("save-bodies-ui-toggled", deferred.resolve);
// The checked state is only updated once the popup is shown.
menuItem.parentNode.openPopup();
});
return deferred.promise;
}

View File

@ -74,58 +74,16 @@ function testPageLoad() {
"Logged network entry is page load");
is(lastRequest.request.method, "GET", "Method is correct");
ok(!lastRequest.request.postData.text, "No request body was stored");
ok(lastRequest.discardRequestBody, "Request body was discarded");
ok(!lastRequest.response.content.text, "No response body was stored");
ok(lastRequest.discardResponseBody || lastRequest.fromCache,
"Response body was discarded or response came from the cache");
lastRequest = null;
requestCallback = null;
executeSoon(testPageLoadBody);
};
content.location = TEST_NETWORK_REQUEST_URI;
}
function testPageLoadBody() {
// Turn on logging of request bodies and check again.
hud.ui.setSaveRequestAndResponseBodies(true).then(() => {
ok(hud.ui._saveRequestAndResponseBodies,
"The saveRequestAndResponseBodies property was successfully set.");
testPageLoadBodyAfterSettingUpdate();
});
}
function testPageLoadBodyAfterSettingUpdate() {
let loaded = false;
let requestCallbackInvoked = false;
requestCallback = function() {
ok(lastRequest, "Page load was logged again");
ok(!lastRequest.discardResponseBody, "Response body was not discarded");
ok(!lastRequest.discardRequestBody, "Request body was not discarded");
is(lastRequest.response.content.text.indexOf("<!DOCTYPE HTML>"), 0,
"Response body's beginning is okay");
lastRequest = null;
requestCallback = null;
requestCallbackInvoked = true;
if (loaded) {
executeSoon(testXhrGet);
}
};
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
loaded = true;
if (requestCallbackInvoked) {
executeSoon(testXhrGet);
}
}, true);
content.location.reload();
content.location = TEST_NETWORK_REQUEST_URI;
}
function testXhrGet() {

View File

@ -367,42 +367,13 @@ WebConsoleFrame.prototype = {
_destroyer: null,
// Used in tests.
_saveRequestAndResponseBodies: false,
_saveRequestAndResponseBodies: true,
// Chevron width at the starting of Web Console's input box.
_chevronWidth: 0,
// Width of the monospace characters in Web Console's input box.
_inputCharWidth: 0,
/**
* Tells whether to save the bodies of network requests and responses.
* Disabled by default to save memory.
*
* @return boolean
* The saveRequestAndResponseBodies pref value.
*/
getSaveRequestAndResponseBodies:
function WCF_getSaveRequestAndResponseBodies() {
let deferred = promise.defer();
let toGet = [
"NetworkMonitor.saveRequestAndResponseBodies"
];
// Make sure the web console client connection is established first.
this.webConsoleClient.getPreferences(toGet, response => {
if (!response.error) {
this._saveRequestAndResponseBodies = response.preferences[toGet[0]];
deferred.resolve(this._saveRequestAndResponseBodies);
}
else {
deferred.reject(response.error);
}
});
return deferred.promise;
},
/**
* Setter for saving of network request and response bodies.
*
@ -560,47 +531,6 @@ WebConsoleFrame.prototype = {
// calculations.
this._updateCharSize();
let updateSaveBodiesPrefUI = (element) => {
this.getSaveRequestAndResponseBodies().then(value => {
element.setAttribute("checked", value);
this.emit("save-bodies-ui-toggled");
});
}
let reverseSaveBodiesPref = ({ target: element }) => {
this.getSaveRequestAndResponseBodies().then(value => {
this.setSaveRequestAndResponseBodies(!value);
element.setAttribute("checked", value);
this.emit("save-bodies-pref-reversed");
});
}
let saveBodiesDisabled = !this.getFilterState("networkinfo") &&
!this.getFilterState("netxhr") &&
!this.getFilterState("network");
let saveBodies = doc.getElementById("saveBodies");
saveBodies.addEventListener("command", reverseSaveBodiesPref);
saveBodies.disabled = saveBodiesDisabled;
let saveBodiesContextMenu = doc.getElementById("saveBodiesContextMenu");
saveBodiesContextMenu.addEventListener("command", reverseSaveBodiesPref);
saveBodiesContextMenu.disabled = saveBodiesDisabled;
saveBodies.parentNode.addEventListener("popupshowing", () => {
updateSaveBodiesPrefUI(saveBodies);
saveBodies.disabled = !this.getFilterState("networkinfo") &&
!this.getFilterState("netxhr") &&
!this.getFilterState("network");
});
saveBodiesContextMenu.parentNode.addEventListener("popupshowing", () => {
updateSaveBodiesPrefUI(saveBodiesContextMenu);
saveBodiesContextMenu.disabled = !this.getFilterState("networkinfo") &&
!this.getFilterState("netxhr") &&
!this.getFilterState("network");
});
let clearButton = doc.getElementsByClassName("webconsole-clear-console-button")[0];
clearButton.addEventListener("command", () => {
this.owner._onClearButton();
@ -951,15 +881,6 @@ WebConsoleFrame.prototype = {
let prefKey = target.getAttribute("prefKey");
this.setFilterState(prefKey, state);
// Disable the log response and request body if network logging is off.
if (prefKey == "networkinfo" || prefKey == "netxhr" || prefKey == "network") {
let checkState = !this.getFilterState("networkinfo") &&
!this.getFilterState("netxhr") &&
!this.getFilterState("network");
this.document.getElementById("saveBodies").disabled = checkState;
this.document.getElementById("saveBodiesContextMenu").disabled = checkState;
}
// Adjust the state of the button appropriately.
let menuPopup = target.parentNode;
@ -5089,8 +5010,13 @@ WebConsoleConnectionProxy.prototype = {
}
this.webConsoleClient = webConsoleClient;
this._hasNativeConsoleAPI = response.nativeConsoleAPI;
// There is no way to view response bodies from the Browser Console, so do
// not waste the memory.
let saveBodies = !this.webConsoleFrame.owner._browserConsole;
this.webConsoleFrame.setSaveRequestAndResponseBodies(saveBodies);
this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);

View File

@ -72,8 +72,6 @@ function goUpdateConsoleCommands() {
<popupset id="mainPopupSet">
<menupopup id="output-contextmenu" onpopupshowing="goUpdateGlobalEditMenuItems()">
<menuitem id="saveBodiesContextMenu" type="checkbox" label="&saveBodies.label;"
accesskey="&saveBodies.accesskey;"/>
<menuitem id="menu_openURL" label="&openURL.label;"
accesskey="&openURL.accesskey;" command="consoleCmd_openURL"
selection="network" selectionType="single"/>
@ -110,9 +108,6 @@ function goUpdateConsoleCommands() {
prefKey="netxhr"/>
<menuitem label="&btnConsoleLog;" type="checkbox" autocheck="false"
prefKey="networkinfo"/>
<menuseparator id="saveBodiesSeparator" />
<menuitem id="saveBodies" type="checkbox" label="&saveBodies.label;"
accesskey="&saveBodies.accesskey;"/>
</menupopup>
</toolbarbutton>
<toolbarbutton label="&btnPageCSS.label;" type="menu-button"

View File

@ -0,0 +1,201 @@
# Memory Tool Architecture
The memory tool is built of three main elements:
1. `JS::ubi::Node` provides an interface to either the live heap graph, or a
serialized, offline snapshot of some heap graph from a previous moment in
time. Our various heap analyses (census, dominator trees, shortest paths,
etc) run on top of `JS::ubi::Node` graphs.
2. The `HeapAnalysesWorker` runs in a worker thread, performing analyses on
snapshots and translating the results into something the frontend can render
simply and quickly.
3. Finally, the last element is the frontend that renders data received from the
`HeapAnalysesClient` to the DOM and translates user input into requests for
new data with the `HeapAnalysesClient`.
Unlike other tools (such as the JavaScript debugger), the memory tool makes very
little use of the Remote Debugger Server and the actors that reside in it. Use
of the [`MemoryActor`](devtools/server/actors/memory.js) is limited to toggling
allocation stack recording on and off, and transferring heap snapshots from the
debuggee (which is on the server) to the `HeapAnalysesWorker` (which is on the
client). A nice benefit that naturally emerges, is that supporting "legacy"
servers (eg, using Firefox Developer Edition as a client to remote debug a
release Firefox for Android server) is a no-op. As we add new analyses, we can
run them on snapshots taken on old servers no problem. The only requirement is
that changes to the snapshot format itself remain backwards compatible.
## `JS::ubi::Node`
`JS::ubi::Node` itself is very well documented in the `js/public/UbiNode.h`
header. I suggest you at least skim that documentation before continuing.
A "heap snapshot" is a representation of the heap graph at some particular past
instance in time.
A "heap analysis" is an algorithm that runs on a `JS::ubi::Node` heap
graph. That's it. Generally, analyses can run on either the live heap graph or a
deserialized snapshot. Example analyses include "census", which aggregates and
counts nodes into various user-specified buckets; "dominator trees", which
compute the "dominates" relation and retained size for all nodes in the heap
graph; and "shortest paths" which finds the shortest paths from the GC roots to
some subset of nodes.
### Saving Heap Snapshots
Saving a heap snapshot has a few requirements:
1. The binary format must remain backwards compatible and future extensible.
2. The live heap graph must not mutate while we are in the process of
serializing it.
3. The act of saving a heap snapshot should impose as little memory overhead as
possible. If we are taking a snapshot to debug frequent out-of-memory errors,
we don't want to trigger an OOM ourselves!
To solve (1), we use the protobuf message format. The message definitions
themselves are in `devtools/shared/heapsnapshot/CoreDump.proto`. We always use
`optional` fields so we can change our mind about what fields are required
sometime in the future. Deserialization checks the semantic integrity of
deserialized protobuf messages.
For (2), we rely on SpiderMonkey's GC rooting hazard static analysis and the
`AutoCheckCannotGC` dynamic analysis to ensure that neither JS nor GC runs and
modifies objects or moves them from one address in memory to another. There is
no equivalent suppression and static analysis technique for the
[cycle collector](https://developer.mozilla.org/en/docs/Interfacing_with_the_XPCOM_cycle_collector),
so care must be taken not to invoke methods that could start cycle collection or
mutate the heap graph from the cycle collector's perspective. At the time of
writing, we don't yet support saving the cycle collector's portion of the heap
graph in snapshots, but that work is deemed Very Important and Very High
Priority.
Finally, (3) imposes upon us that we do not build the serialized heap snapshot
binary blob in memory, but instead stream it out to disk while generating it.
Once all of that is accounted for, saving snapshots becomes pretty straight
forward. We traverse the live heap graph with `JS::ubi::Node` and
`JS::ubi::BreadthFirst`, create a protobuf message for each node and each node's
edges, and write these messages to disk before continuing the traversal to the
next node.
This functionality is exposed to chrome JavaScript as the
`[ThreadSafe]ChromeUtils.saveHeapSnapshot` function. See
`dom/webidl/ThreadSafeChromeUtils.webidl` for API documentation.
### Reading Heap Snapshots
Reading heap snapshots has less restrictions than saving heap snapshots. The
protobuf messages that make up the core dump are deserialized one by one, stored
as a set of `DeserializedNode`s and a set of `DeserializedEdge`s, and the result
is a `HeapSnapshot` instance.
The `DeserializedNode` and `DeserializedEdge` classes implement the
`JS::ubi::Node` interface. Analyses running on offline heap snapshots rather
than the live heap graph operate on these classes (unknowingly, of course).
For more details, see the
[`mozilla::devtools::HeapSnapshot`](devtools/shared/heapsnapshot/HeapSnapshot.cpp)
and
[`mozilla::devtools::Deserialized{Node,Edge}`](devtools/shared/heapsnapshot/DeserializedNode.h)
classes.
### Heap Analyses
Heap analyses operate on `JS::ubi::Node` graphs without knowledge of whether
that graph is backed by the live heap graph or an offline heap snapshot. They
must make sure never to allocate GC things or modify the live heap graph.
In general, analyses are implemented in their own `js/public/UbiFooBar.h` header
(eg `js/public/UbiCensus.h`), and are exposed to chrome JavaScript code via a
method on the [`HeapSnapshot`](dom/webidl/HeapSnapshot.webidl) webidl
interface.
For each analysis we expose to chrome JavaScript on the `HeapSnapshot` webidl
interface, there is a small amount of glue code in Gecko. The
[`mozilla::devtools::HeapSnapshot`](devtools/shared/heapsnapshot/HeapSnapshot.h)
C++ class implements the webidl interface. The analyses methods (eg
`ComputeDominatorTree`) take the deserialized nodes and edges from the heap
snapshot, create `JS::ubi::Node`s from them, call the analyses from
`js/public/Ubi*.h`, and wrap the results in something that can be represented in
JavaScript.
For API documentation on running specific analyses, see the
[`HeapSnapshot`](dom/webidl/HeapSnapshot.webidl) webidl interface.
### Testing `JS::ubi::Node`, Snapshots, and Analyses
The majority of the tests reside within `devtools/shared/heapsnapshot/tests/**`.
For reading and saving heap snapshots, most tests are gtests. The gtests can be
run with the `mach gtest DevTools.*` command. The rest are integration sanity
tests to make sure we can read and save snapshots in various environments, such
as xpcshell or workers. These can be run with the usual `mach test $PATH`
commands.
There are also `JS::ubi::Node` related unit tests in
`js/src/jit-test/tests/heap-analysis/*`, `js/src/jit-test/tests/debug/Memory-*`,
and `js/src/jsapi-tests/testUbiNode.cpp`. See
https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Running_Automated_JavaScript_Tests#Running_jit-tests
for running the JIT tests.
## `HeapAnalysesWorker`
The `HeapAnalysesWorker` orchestrates running specific analyses on snapshots and
transforming the results into something that can simply and quickly be rendered
by the frontend. The analyses can take some time to run (sometimes on the order
of seconds), so doing them in a worker thread allows the interface to stay
responsive. The `HeapAnalysisClient` provides the main thread's interface to the
worker.
The `HeapAnalysesWorker` doesn't actually do much itself; mostly just shuffling
data and transforming it from one representation to another or calling utility
functions that do those things. Most of these are implemented as traversals of
the resulting census or dominator trees.
See the
`devtools/shared/heapsnapshot/{CensusUtils,CensusTreeNode,DominatorTreeNode}.js`
files for details on the various data transformations and shuffling that the
`HeapAnalysesWorker` delegates to.
### Testing the `HeapAnalysesWorker` and `HeapAnalysesClient`
Tests for the `HeapAnalysesWorker` and `HeapAnalysesClient` reside in
`devtools/shared/heapsnapshot/tests/**` and can be run with the usual `mach test
$PATH` command.
## Frontend
The frontend of the memory tool is built with React and Redux.
[React has thorough documentation.](https://facebook.github.io/react/)
[Redux has thorough documentation.](http://rackt.org/redux/index.html)
We have React components in `devtools/client/memory/components/*`.
We have Redux reducers in `devtools/client/memory/reducers/*`.
We have Redux actions and action-creating tasks in
`devtools/client/memory/actions/*`.
React components should be pure functions from their props to the rendered
(virtual) DOM. Redux reducers should also be observably pure.
Impurity within the frontend is confined to the tasks that are creating and
dispatching actions. All communication with the outside world (such as the
`HeapAnalysesWorker`, the Remote Debugger Server, or the file system) is
restricted to within these tasks.
### Testing the Frontend
Unit tests for React components are in `devtools/client/memory/test/chrome/*`.
Unit tests for actions, reducers, and state changes are in
`devtools/client/memory/test/unit/*`.
Holistic integration tests for the frontend and the whole memory tool are in
`devtools/client/memory/test/browser/*`.
All tests can be run with the usual `mach test $PATH` command.

View File

@ -46,7 +46,7 @@ Parser.prototype = {
// all the scripts. Fastest/easiest way is with a regular expression.
// Don't worry, the rules of using a <script> tag are really strict,
// this will work.
let regexp = /<script[^>]*>([^]*?)<\/script\s*>/gim;
let regexp = /<script[^>]*?(?:>([^]*?)<\/script\s*>|\/>)/gim;
let syntaxTrees = [];
let scriptMatches = [];
let scriptMatch;
@ -54,7 +54,9 @@ Parser.prototype = {
if (aSource.match(/^\s*</)) {
// First non whitespace character is &lt, so most definitely HTML.
while (scriptMatch = regexp.exec(aSource)) {
scriptMatches.push(scriptMatch[1]); // Contents are captured at index 1.
// Contents are captured at index 1 or nothing: Self-closing scripts
// won't capture code content
scriptMatches.push(scriptMatch[1] || "");
}
}

View File

@ -524,11 +524,10 @@ NetworkMonitor.prototype = {
owner: null,
/**
* Whether to save the bodies of network requests and responses. Disabled by
* default to save memory.
* Whether to save the bodies of network requests and responses.
* @type boolean
*/
saveRequestAndResponseBodies: false,
saveRequestAndResponseBodies: true,
/**
* Object that holds the HTTP activity objects for ongoing requests.
@ -1243,7 +1242,7 @@ NetworkMonitorChild.prototype = {
appId: null,
owner: null,
_netEvents: null,
_saveRequestAndResponseBodies: false,
_saveRequestAndResponseBodies: true,
get saveRequestAndResponseBodies() {
return this._saveRequestAndResponseBodies;

View File

@ -87,7 +87,7 @@ function onNetworkEventUpdate(aState, aType, aPacket)
status: "200",
statusText: "OK",
headersSize: /^\d+$/,
discardResponseBody: true,
discardResponseBody: false,
},
};
break;
@ -104,8 +104,8 @@ function onNetworkEventUpdate(aState, aType, aPacket)
case "responseContent":
expectedPacket = {
mimeType: "application/json",
contentSize: 0,
discardResponseBody: true,
contentSize: 1070,
discardResponseBody: false,
};
break;
case "eventTimings":
@ -180,7 +180,7 @@ function onRequestPostData(aState, aResponse)
info("checking request POST data");
ok(!aResponse.postData.text, "no request POST data");
ok(aResponse.postDataDiscarded, "request POST data was discarded");
ok(!aResponse.postDataDiscarded, "request POST data was not discarded");
onResponseHeaders = onResponseHeaders.bind(null, aState);
aState.client.getResponseHeaders(aState.netActor,
@ -225,8 +225,8 @@ function onResponseContent(aState, aResponse)
{
info("checking response content");
ok(!aResponse.content.text, "no response content");
ok(aResponse.contentDiscarded, "response content was discarded");
ok(aResponse.content.text, "response content text");
ok(!aResponse.contentDiscarded, "response content was not discarded");
onEventTimings = onEventTimings.bind(null, aState);
aState.client.getEventTimings(aState.netActor,

View File

@ -25,7 +25,7 @@ function startTest()
function onAttach(aState, aResponse)
{
info("enable network request and response body logging");
info("set long string length");
window.ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
window.ORIGINAL_LONG_STRING_INITIAL_LENGTH =
@ -34,18 +34,6 @@ function onAttach(aState, aResponse)
DebuggerServer.LONG_STRING_LENGTH = 400;
DebuggerServer.LONG_STRING_INITIAL_LENGTH = 400;
onSetPreferences = onSetPreferences.bind(null, aState);
aState.client.setPreferences({
"NetworkMonitor.saveRequestAndResponseBodies": true,
}, onSetPreferences);
}
function onSetPreferences(aState, aResponse)
{
is(aResponse.updated.length, 1, "updated prefs length");
is(aResponse.updated[0], "NetworkMonitor.saveRequestAndResponseBodies",
"updated prefs length");
info("test network POST request");
onNetworkEvent = onNetworkEvent.bind(null, aState);

View File

@ -25,20 +25,6 @@ function startTest()
function onAttach(aState, aResponse)
{
info("enable network request and response body logging");
onSetPreferences = onSetPreferences.bind(null, aState);
aState.client.setPreferences({
"NetworkMonitor.saveRequestAndResponseBodies": true,
}, onSetPreferences);
}
function onSetPreferences(aState, aResponse)
{
is(aResponse.updated.length, 1, "updated prefs length");
is(aResponse.updated[0], "NetworkMonitor.saveRequestAndResponseBodies",
"updated prefs length");
info("test network POST request");
onNetworkEvent = onNetworkEvent.bind(null, aState);

View File

@ -403,7 +403,7 @@ public class BrowserApp extends GeckoApp
// This flow is from the option menu which has check to see if a bookmark was already added.
// So, it is safe here to show the snackbar that bookmark_added without any checks.
final SnackbarCallback callback = new SnackbarCallback() {
final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
@Override
public void onClick(View v) {
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "bookmark_options");
@ -411,18 +411,19 @@ public class BrowserApp extends GeckoApp
}
};
showSnackbar(getResources().getString(R.string.bookmark_added),
SnackbarHelper.showSnackbarWithAction(this,
getResources().getString(R.string.bookmark_added),
Snackbar.LENGTH_LONG,
getResources().getString(R.string.bookmark_options),
callback);
}
private void showBookmarkRemovedSnackbar() {
showSnackbar(getResources().getString(R.string.bookmark_removed), Snackbar.LENGTH_SHORT, null, null);
SnackbarHelper.showSnackbar(this, getResources().getString(R.string.bookmark_removed), Snackbar.LENGTH_SHORT);
}
private void showSwitchToReadingListSnackbar(String message) {
final SnackbarCallback callback = new SnackbarCallback() {
final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
@Override
public void onClick(View v) {
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "reading_list");
@ -432,7 +433,11 @@ public class BrowserApp extends GeckoApp
}
};
showSnackbar(message, Snackbar.LENGTH_LONG, getResources().getString(R.string.switch_button_message), callback);
SnackbarHelper.showSnackbarWithAction(this,
message,
Snackbar.LENGTH_LONG,
getResources().getString(R.string.switch_button_message),
callback);
}
public void onAddedToReadingList(String url) {
@ -444,7 +449,9 @@ public class BrowserApp extends GeckoApp
}
public void onRemovedFromReadingList(String url) {
showSnackbar(getResources().getString(R.string.reading_list_removed), Snackbar.LENGTH_SHORT, null, null);
SnackbarHelper.showSnackbar(this,
getResources().getString(R.string.reading_list_removed),
Snackbar.LENGTH_SHORT);
}
@Override
@ -2726,10 +2733,8 @@ public class BrowserApp extends GeckoApp
@Override
public boolean onInterceptTouchEvent(View view, MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN
&& mSnackbar != null
&& mSnackbar.isShown()) {
mSnackbar.dismiss();
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
SnackbarHelper.dismissCurrentSnackbar();
}
// Only try to hide the button toast if it's already inflated and if we are starting a tap action.
@ -3731,7 +3736,7 @@ public class BrowserApp extends GeckoApp
// hold a reference to the Tab itself in the anonymous listener class.
final int newTabId = newTab.getId();
final SnackbarCallback callback = new SnackbarCallback() {
final SnackbarHelper.SnackbarCallback callback = new SnackbarHelper.SnackbarCallback() {
@Override
public void onClick(View v) {
Telemetry.sendUIEvent(TelemetryContract.Event.SHOW, TelemetryContract.Method.TOAST, "switchtab");
@ -3745,7 +3750,7 @@ public class BrowserApp extends GeckoApp
getResources().getString(R.string.new_tab_opened);
final String buttonMessage = getResources().getString(R.string.switch_button_message);
showSnackbar(message, Snackbar.LENGTH_LONG, buttonMessage, callback);
SnackbarHelper.showSnackbarWithAction(this, message, Snackbar.LENGTH_LONG, buttonMessage, callback);
}
// BrowserSearch.OnSearchListener

View File

@ -217,8 +217,9 @@ public class EditBookmarkDialog {
@Override
public void onPostExecute(Void result) {
View view = ((Activity)context).findViewById(android.R.id.content);
Snackbar.make(view, R.string.bookmark_updated, Snackbar.LENGTH_SHORT).show();
SnackbarHelper.showSnackbar((Activity) context,
context.getString(R.string.bookmark_updated),
Snackbar.LENGTH_SHORT);
}
}).execute();
}

View File

@ -5,7 +5,6 @@
package org.mozilla.gecko;
import org.mozilla.gecko.AppConstants;
import android.widget.AdapterView;
import android.widget.Button;
import org.mozilla.gecko.AppConstants.Versions;
@ -50,7 +49,6 @@ import org.mozilla.gecko.widget.ButtonToast;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -76,9 +74,7 @@ import android.os.Process;
import android.os.StrictMode;
import android.provider.ContactsContract;
import android.provider.MediaStore.Images.Media;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Base64;
@ -196,7 +192,6 @@ public abstract class GeckoApp
protected DoorHangerPopup mDoorHangerPopup;
protected FormAssistPopup mFormAssistPopup;
protected ButtonToast mToast;
protected Snackbar mSnackbar;
protected LayerView mLayerView;
private AbsoluteLayout mPluginContainer;
@ -579,7 +574,7 @@ public abstract class GeckoApp
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
showSnackbar(getString(resId), Snackbar.LENGTH_SHORT, null, null);
SnackbarHelper.showSnackbar(GeckoApp.this, getString(resId), Snackbar.LENGTH_SHORT);
}
});
}
@ -651,17 +646,7 @@ public abstract class GeckoApp
Telemetry.sendUIEvent(TelemetryContract.Event.SHARE, TelemetryContract.Method.LIST, "text");
} else if ("Snackbar:Show".equals(event)) {
final String msg = message.getString("message");
final int duration = message.getInt("duration");
NativeJSObject action = message.optObject("action", null);
final SnackbarEventCallback snackbarCallback = new SnackbarEventCallback(callback);
showSnackbar(msg,
duration,
action != null ? action.optString("label", null) : null,
snackbarCallback);
SnackbarHelper.showSnackbar(this, message, callback);
} else if ("SystemUI:Visibility".equals(event)) {
setSystemUiVisible(message.getBoolean("visible"));
@ -844,58 +829,6 @@ public abstract class GeckoApp
return mToast;
}
void showSnackbar(final String message, final int duration, @Nullable final String action,
final @Nullable SnackbarCallback callback) {
final Snackbar snackbar = Snackbar.make(mRootLayout, message, duration);
if (callback != null && !TextUtils.isEmpty(action)) {
snackbar.setAction(action, callback);
snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.fennec_ui_orange));
snackbar.setCallback(callback);
}
snackbar.show();
this.mSnackbar = snackbar;
}
/**
* Combined interface for handling all callbacks from a snackbar because anonymous classes can only extend one
* interface or class.
*/
public static abstract class SnackbarCallback extends Snackbar.Callback implements View.OnClickListener {};
/**
* SnackbarCallback implementation for delegating snackbar events to an EventCallback.
*/
private static class SnackbarEventCallback extends SnackbarCallback {
private EventCallback callback;
public SnackbarEventCallback(EventCallback callback) {
this.callback = callback;
}
@Override
public synchronized void onClick(View view) {
if (callback == null) {
return;
}
callback.sendSuccess(null);
callback = null;
}
@Override
public synchronized void onDismissed(Snackbar snackbar, int event) {
if (callback == null || event == Snackbar.Callback.DISMISS_EVENT_ACTION) {
return;
}
callback.sendError(null);
callback = null;
}
}
void showButtonToast(final String message, final String duration,
final String buttonText, final String buttonIcon,
final String buttonId) {
@ -1061,12 +994,12 @@ public abstract class GeckoApp
File dcimDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (!dcimDir.mkdirs() && !dcimDir.isDirectory()) {
showSnackbar(getString(R.string.set_image_path_fail), Snackbar.LENGTH_SHORT, null, null);
SnackbarHelper.showSnackbar(this, getString(R.string.set_image_path_fail), Snackbar.LENGTH_SHORT);
return;
}
String path = Media.insertImage(getContentResolver(),image, null, null);
if (path == null) {
showSnackbar(getString(R.string.set_image_path_fail), Snackbar.LENGTH_SHORT, null, null);
SnackbarHelper.showSnackbar(this, getString(R.string.set_image_path_fail), Snackbar.LENGTH_SHORT);
return;
}
final Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
@ -1083,7 +1016,7 @@ public abstract class GeckoApp
};
ActivityHandlerHelper.startIntentForActivity(this, chooser, handler);
} else {
showSnackbar(getString(R.string.set_image_fail), Snackbar.LENGTH_SHORT, null, null);
SnackbarHelper.showSnackbar(this, getString(R.string.set_image_fail), Snackbar.LENGTH_SHORT);
}
} catch(OutOfMemoryError ome) {
Log.e(LOGTAG, "Out of Memory when converting to byte array", ome);

View File

@ -2673,8 +2673,10 @@ public class GeckoAppShell
// Don't fail silently, tell the user that we weren't able to share the image
private static final void showImageShareFailureSnackbar() {
View rootView = ((Activity)getContext()).findViewById(android.R.id.content);
Snackbar.make(rootView, R.string.share_image_failed, Snackbar.LENGTH_SHORT).show();
SnackbarHelper.showSnackbar((Activity) getContext(),
getApplicationContext().getString(R.string.share_image_failed),
Snackbar.LENGTH_SHORT
);
}
@WrapForJNI(allowMultithread = true)

View File

@ -0,0 +1,143 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.NativeJSObject;
import android.app.Activity;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.text.TextUtils;
import android.view.View;
import java.lang.ref.WeakReference;
/**
* Helper class for creating and dismissing snackbars. Use this class to guarantee a consistent style and behavior
* across the app.
*/
public class SnackbarHelper {
/**
* Combined interface for handling all callbacks from a snackbar because anonymous classes can only extend one
* interface or class.
*/
public static abstract class SnackbarCallback extends Snackbar.Callback implements View.OnClickListener {}
/**
* SnackbarCallback implementation for delegating snackbar events to an EventCallback.
*/
private static class SnackbarEventCallback extends SnackbarCallback {
private EventCallback callback;
public SnackbarEventCallback(EventCallback callback) {
this.callback = callback;
}
@Override
public synchronized void onClick(View view) {
if (callback == null) {
return;
}
callback.sendSuccess(null);
callback = null; // Releasing reference. We only want to execute the callback once.
}
@Override
public synchronized void onDismissed(Snackbar snackbar, int event) {
if (callback == null || event == Snackbar.Callback.DISMISS_EVENT_ACTION) {
return;
}
callback.sendError(null);
callback = null; // Releasing reference. We only want to execute the callback once.
}
}
private static final Object currentSnackbarLock = new Object();
private static WeakReference<Snackbar> currentSnackbar = new WeakReference<>(null); // Guarded by 'currentSnackbarLock'
/**
* Show a snackbar to display a message.
*
* @param activity Activity to show the snackbar in.
* @param message The text to show. Can be formatted text.
* @param duration How long to display the message.
*/
public static void showSnackbar(Activity activity, String message, int duration) {
showSnackbarWithAction(activity, message, duration, null, null);
}
/**
* Build and show a snackbar from a Gecko Snackbar:Show event.
*/
public static void showSnackbar(Activity activity, final NativeJSObject object, final EventCallback callback) {
final String message = object.getString("message");
final int duration = object.getInt("duration");
NativeJSObject action = object.optObject("action", null);
showSnackbarWithAction(activity,
message,
duration,
action != null ? action.optString("label", null) : null,
new SnackbarHelper.SnackbarEventCallback(callback));
}
/**
* Show a snackbar to display a message and an action.
*
* @param activity Activity to show the snackbar in.
* @param message The text to show. Can be formatted text.
* @param duration How long to display the message.
* @param action Action text to display.
* @param callback Callback to be invoked when the action is clicked or the snackbar is dismissed.
*/
public static void showSnackbarWithAction(Activity activity, String message, int duration, String action, SnackbarCallback callback) {
final View parentView = findBestParentView(activity);
final Snackbar snackbar = Snackbar.make(parentView, message, duration);
if (callback != null && !TextUtils.isEmpty(action)) {
snackbar.setAction(action, callback);
snackbar.setActionTextColor(ContextCompat.getColor(activity, R.color.fennec_ui_orange));
snackbar.setCallback(callback);
}
snackbar.show();
synchronized (currentSnackbarLock) {
currentSnackbar = new WeakReference<>(snackbar);
}
}
/**
* Dismiss the currently visible snackbar.
*/
public static void dismissCurrentSnackbar() {
synchronized (currentSnackbarLock) {
final Snackbar snackbar = currentSnackbar.get();
if (snackbar != null && snackbar.isShown()) {
snackbar.dismiss();
}
}
}
/**
* Find the best parent view to hold the Snackbar's view. The Snackbar implementation of the support
* library will use this view to walk up the view tree to find an actual suitable parent (if needed).
*/
private static View findBestParentView(Activity activity) {
if (activity instanceof GeckoApp) {
final View view = activity.findViewById(R.id.root_layout);
if (view != null) {
return view;
}
}
return activity.findViewById(android.R.id.content);
}
}

View File

@ -21,8 +21,8 @@ import java.util.List;
return Arrays.asList(
new DownloadContent.Builder()
.setId("bff50e08-7bbc-4d77-a907-bb0a54434bee")
.setLocation("fonts/CharisSILCompact-B.ttf.gz")
.setId("c40929cf-7f4c-fa72-3dc9-12cadf56905d")
.setLocation("fonts/ff7ecae7669a51d5fa6a5f8e703278ebda3a68f51bc49c4321bde4438020d639.gz")
.setFilename("CharisSILCompact-B.ttf")
.setChecksum("699d958b492eda0cc2823535f8567d0393090e3842f6df3c36dbe7239cb80b6d")
.setDownloadChecksum("ff7ecae7669a51d5fa6a5f8e703278ebda3a68f51bc49c4321bde4438020d639")
@ -32,8 +32,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("68c6472d-94a6-4fb2-8525-78e427b022fe")
.setLocation("fonts/CharisSILCompact-BI.ttf.gz")
.setId("6d265876-85ed-0917-fdc8-baf583ca2cba")
.setLocation("fonts/dfb6d583edd27d5e6d91d479e6c8a5706275662c940c65b70911493bb279904a.gz")
.setFilename("CharisSILCompact-BI.ttf")
.setChecksum("82465e747b4f41471dbfd942842b2ee810749217d44b55dbc43623b89f9c7d9b")
.setDownloadChecksum("dfb6d583edd27d5e6d91d479e6c8a5706275662c940c65b70911493bb279904a")
@ -43,8 +43,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("33d0ce0d-9c48-4a37-8b74-81cce872061b")
.setLocation("fonts/CharisSILCompact-I.ttf.gz")
.setId("8460dc6d-d129-fd1a-24b6-343dbf6531dd")
.setLocation("fonts/5a257ec3c5226e7be0be65e463f5b22eff108da853b9ff7bc47f1733b1ddacf2.gz")
.setFilename("CharisSILCompact-I.ttf")
.setChecksum("ab3ed6f2a4d4c2095b78227bd33155d7ccd05a879c107a291912640d4d64f767")
.setDownloadChecksum("5a257ec3c5226e7be0be65e463f5b22eff108da853b9ff7bc47f1733b1ddacf2")
@ -54,8 +54,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("7e274cdc-4216-4dc0-b7a5-8ec333f0c287")
.setLocation("fonts/CharisSILCompact-R.ttf.gz")
.setId("c906275c-3747-fe27-426f-6187526a6f06")
.setLocation("fonts/cab284228b8dfe8ef46c3f1af70b5b6f9e92878f05e741ecc611e5e750a4a3b3.gz")
.setFilename("CharisSILCompact-R.ttf")
.setChecksum("4ed509317f1bb441b185ea13bf1c9d19d1a0b396962efa3b5dc3190ad88f2067")
.setDownloadChecksum("cab284228b8dfe8ef46c3f1af70b5b6f9e92878f05e741ecc611e5e750a4a3b3")
@ -65,8 +65,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("b144002f-d5de-448c-8952-da1e405e022f")
.setLocation("fonts/ClearSans-Bold.ttf.gz")
.setId("ff5deecc-6ecc-d816-bb51-65face460119")
.setLocation("fonts/d95168996dc932e6504cb5448fcb759e0ee6e66c5c8603293b046d28ab589cce.gz")
.setFilename("ClearSans-Bold.ttf")
.setChecksum("385d0a293c1714770e198f7c762ab32f7905a0ed9d2993f69d640bd7232b4b70")
.setDownloadChecksum("d95168996dc932e6504cb5448fcb759e0ee6e66c5c8603293b046d28ab589cce")
@ -76,8 +76,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("f07502f5-e4c5-41a8-8788-89717397a98d")
.setLocation("fonts/ClearSans-BoldItalic.ttf.gz")
.setId("a173d1db-373b-ce42-1335-6b3285cfdebd")
.setLocation("fonts/f5e18f4acc4ceaeca9e081b1be79cd6034e0dc7ad683fa240195fd6c838452e0.gz")
.setFilename("ClearSans-BoldItalic.ttf")
.setChecksum("7bce66864e38eecd7c94b6657b9b38c35ebfacf8046bfb1247e08f07fe933198")
.setDownloadChecksum("f5e18f4acc4ceaeca9e081b1be79cd6034e0dc7ad683fa240195fd6c838452e0")
@ -87,8 +87,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("afafc7ef-f516-42da-88d4-d8435f65541b")
.setLocation("fonts/ClearSans-Italic.ttf.gz")
.setId("e65c66df-0088-940d-ca5c-207c22118c0e")
.setLocation("fonts/56d12114ac15d913d7d9876c698889cd25f26e14966a8bd7424aeb0f61ffaf87.gz")
.setFilename("ClearSans-Italic.ttf")
.setChecksum("87c13c5fbae832e4f85c3bd46fcbc175978234f39be5fe51c4937be4cbff3b68")
.setDownloadChecksum("56d12114ac15d913d7d9876c698889cd25f26e14966a8bd7424aeb0f61ffaf87")
@ -98,8 +98,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("28521d9b-ac2e-45d0-89b6-a4c9076dbf6d")
.setLocation("fonts/ClearSans-Light.ttf.gz")
.setId("25610abb-5dc8-fd75-40e7-990507f010c4")
.setLocation("fonts/1fc716662866b9c01e32dda3fc9c54ca3e57de8c6ac523f46305d8ae6c0a9cf4.gz")
.setFilename("ClearSans-Light.ttf")
.setChecksum("e4885f6188e7a8587f5621c077c6c1f5e8d3739dffc8f4d055c2ba87568c750a")
.setDownloadChecksum("1fc716662866b9c01e32dda3fc9c54ca3e57de8c6ac523f46305d8ae6c0a9cf4")
@ -109,8 +109,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("13f01bf4-da71-4673-9c60-ec0e9a45c38c")
.setLocation("fonts/ClearSans-Medium.ttf.gz")
.setId("ffe40339-a096-2262-c3f8-54af75c81fe6")
.setLocation("fonts/a29184ec6621dbd3bc6ae1e30bba70c479d1001bca647ea4a205ecb64d5a00a0.gz")
.setFilename("ClearSans-Medium.ttf")
.setChecksum("5d0e0115f3a3ed4be3eda6d7eabb899bb9a361292802e763d53c72e00f629da1")
.setDownloadChecksum("a29184ec6621dbd3bc6ae1e30bba70c479d1001bca647ea4a205ecb64d5a00a0")
@ -120,8 +120,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("73104370-c7ee-4b5b-bb37-392a4e66f65a")
.setLocation("fonts/ClearSans-MediumItalic.ttf.gz")
.setId("139a94be-ac69-0264-c9cc-8f2d071fd29d")
.setLocation("fonts/a381a3d4060e993af440a7b72fed29fa3a488536cc451d7c435d5fae1256318b.gz")
.setFilename("ClearSans-MediumItalic.ttf")
.setChecksum("937dda88b26469306258527d38e42c95e27e7ebb9f05bd1d7c5d706a3c9541d7")
.setDownloadChecksum("a381a3d4060e993af440a7b72fed29fa3a488536cc451d7c435d5fae1256318b")
@ -131,8 +131,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("274f3718-f6e0-40b4-b52a-44812ec3ea9e")
.setLocation("fonts/ClearSans-Regular.ttf.gz")
.setId("b887012a-01e1-7c94-fdcb-ca44d5b974a2")
.setLocation("fonts/87dec7f0331e19b293fc510f2764b9bd1b94595ac279cf9414f8d03c5bf34dca.gz")
.setFilename("ClearSans-Regular.ttf")
.setChecksum("9b91bbdb95ffa6663da24fdaa8ee06060cd0a4d2dceaf1ffbdda00e04915ee5b")
.setDownloadChecksum("87dec7f0331e19b293fc510f2764b9bd1b94595ac279cf9414f8d03c5bf34dca")
@ -142,8 +142,8 @@ import java.util.List;
.build(),
new DownloadContent.Builder()
.setId("77803858-3cfb-4a0d-a1d3-fa1bf8a6c604")
.setLocation("fonts/ClearSans-Thin.ttf.gz")
.setId("c8703652-d317-0356-0bf8-95441a5b2c9b")
.setLocation("fonts/64300b48b2867e5642212690f0ff9ea3988f47790311c444a81d25213b4102aa.gz")
.setFilename("ClearSans-Thin.ttf")
.setChecksum("07b0db85a3ad99afeb803f0f35631436a7b4c67ac66d0c7f77d26a47357c592a")
.setDownloadChecksum("64300b48b2867e5642212690f0ff9ea3988f47790311c444a81d25213b4102aa")

View File

@ -15,6 +15,7 @@ import org.mozilla.gecko.GeckoProfile;
import org.mozilla.gecko.R;
import org.mozilla.gecko.ReaderModeUtils;
import org.mozilla.gecko.Restrictions;
import org.mozilla.gecko.SnackbarHelper;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.db.BrowserDB;
@ -413,8 +414,9 @@ public abstract class HomeFragment extends Fragment {
@Override
public void onPostExecute(Void result) {
View rootView = ((Activity)mContext).findViewById(android.R.id.content);
Snackbar.make(rootView, R.string.page_removed, Snackbar.LENGTH_SHORT).show();
SnackbarHelper.showSnackbar((Activity) mContext,
mContext.getString(R.string.page_removed),
Snackbar.LENGTH_SHORT);
}
}
}

View File

@ -25,6 +25,7 @@ import org.mozilla.gecko.Locales;
import org.mozilla.gecko.PrefsHelper;
import org.mozilla.gecko.R;
import org.mozilla.gecko.Restrictions;
import org.mozilla.gecko.SnackbarHelper;
import org.mozilla.gecko.Telemetry;
import org.mozilla.gecko.TelemetryContract;
import org.mozilla.gecko.TelemetryContract.Method;
@ -34,9 +35,12 @@ import org.mozilla.gecko.restrictions.Restrictable;
import org.mozilla.gecko.tabqueue.TabQueueHelper;
import org.mozilla.gecko.updater.UpdateService;
import org.mozilla.gecko.updater.UpdateServiceHelper;
import org.mozilla.gecko.util.EventCallback;
import org.mozilla.gecko.util.GeckoEventListener;
import org.mozilla.gecko.util.HardwareUtils;
import org.mozilla.gecko.util.InputOptionsUtils;
import org.mozilla.gecko.util.NativeEventListener;
import org.mozilla.gecko.util.NativeJSObject;
import org.mozilla.gecko.util.ThreadUtils;
import org.mozilla.gecko.widget.FloatingHintEditText;
@ -92,6 +96,7 @@ extends PreferenceActivity
implements
GeckoActivityStatus,
GeckoEventListener,
NativeEventListener,
OnPreferenceChangeListener,
OnSharedPreferenceChangeListener
{
@ -361,9 +366,12 @@ OnSharedPreferenceChangeListener
addPreferencesFromResource(res);
}
EventDispatcher.getInstance().registerGeckoThreadListener(this,
EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
"Sanitize:Finished");
EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
"Snackbar:Show");
// Add handling for long-press click.
// This is only for Android 3.0 and below (which use the long-press-context-menu paradigm).
final ListView mListView = getListView();
@ -512,8 +520,12 @@ OnSharedPreferenceChangeListener
@Override
protected void onDestroy() {
super.onDestroy();
EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
"Sanitize:Finished");
EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
"Snackbar:Show");
if (mPrefsRequestId > 0) {
PrefsHelper.removeObserver(mPrefsRequestId);
}
@ -617,19 +629,23 @@ OnSharedPreferenceChangeListener
if (event.equals("Sanitize:Finished")) {
boolean success = message.getBoolean("success");
final int stringRes = success ? R.string.private_data_success : R.string.private_data_fail;
final Context context = this;
ThreadUtils.postToUiThread(new Runnable () {
@Override
public void run() {
Snackbar.make(findViewById(android.R.id.content), stringRes, Snackbar.LENGTH_SHORT).show();
}
});
SnackbarHelper.showSnackbar(GeckoPreferences.this,
getString(stringRes),
Snackbar.LENGTH_SHORT);
}
} catch (Exception e) {
Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
}
}
@Override
public void handleMessage(final String event, final NativeJSObject message, final EventCallback callback) {
if ("Snackbar:Show".equals(event)) {
SnackbarHelper.showSnackbar(this, message, callback);
}
}
/**
* Initialize all of the preferences (native of Gecko ones) for this screen.
*

View File

@ -7,6 +7,7 @@ package org.mozilla.gecko.preferences;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.R;
import org.mozilla.gecko.SnackbarHelper;
import org.mozilla.gecko.favicons.Favicons;
import org.mozilla.gecko.favicons.OnFaviconLoadedListener;
import org.mozilla.gecko.favicons.decoders.FaviconDecoder;
@ -89,13 +90,12 @@ public class SearchEnginePreference extends CustomListPreference {
// If this is the last engine, then we are the default, and none of the options
// on this menu can do anything.
if (mParentCategory.getPreferenceCount() == 1) {
ThreadUtils.postToUiThread(new Runnable() {
@Override
public void run() {
Activity activity = (Activity) getContext();
Snackbar.make(activity.findViewById(android.R.id.content), R.string.pref_search_last_toast, Snackbar.LENGTH_SHORT).show();
}
});
SnackbarHelper.showSnackbar(activity,
activity.getString(R.string.pref_search_last_toast),
Snackbar.LENGTH_SHORT);
return;
}

View File

@ -509,6 +509,7 @@ gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
'SharedPreferencesHelper.java',
'SiteIdentity.java',
'SmsManager.java',
'SnackbarHelper.java',
'sqlite/ByteBufferInputStream.java',
'sqlite/MatrixBlobCursor.java',
'sqlite/SQLiteBridge.java',

View File

@ -82,6 +82,6 @@ ident:
merge-%:
ifdef LOCALE_MERGEDIR
$(RM) -rf $(LOCALE_MERGEDIR)
MACOSX_DEPLOYMENT_TARGET= compare-locales -m $(LOCALE_MERGEDIR) $(srcdir)/l10n.ini $(L10NBASEDIR) $*
$(topsrcdir)/mach compare-locales --merge-dir $(LOCALE_MERGEDIR) $*
endif
@echo

View File

@ -71,7 +71,7 @@ xul|scrollbarbutton[sbattr="scrollbar-bottom-top"] {
display: none;
}
xul|thumb {
xul|scrollbar xul|thumb {
background-color: rgba(0, 0, 0, 0.4) !important;
-moz-border-top-colors: none !important;
-moz-border-bottom-colors: none !important;

View File

@ -70,7 +70,7 @@ class CompareLocales(MachCommandBase):
merge_dir = mozpath.join(
self.topobjdir,
self.substs['MOZ_BUILD_APP'],
'locales', 'merge-{ab_CD}'
'locales', 'merge-dir-{ab_CD}'
)
except Exception:
pass

View File

@ -31,6 +31,21 @@ class LocalesMixin(ChunkingMixin):
self.gecko_locale_revisions = None
self.l10n_revisions = {}
def _get_mach_executable(self):
python = self.query_exe('python2.7')
return [python, 'mach']
def _mach(self, target, env, error_list=None, halt_on_failure=True,
output_parser=None):
dirs = self.query_abs_dirs()
mach = self._get_mach_executable()
return self.run_command(mach + target,
error_list=error_list,
halt_on_failure=True,
env=env,
cwd=dirs['abs_mozilla_dir'],
output_parser=None)
def query_locales(self):
if self.locales is not None:
return self.locales
@ -141,19 +156,12 @@ class LocalesMixin(ChunkingMixin):
def run_compare_locales(self, locale, halt_on_failure=False):
dirs = self.query_abs_dirs()
compare_locales_script = os.path.join(dirs['abs_compare_locales_dir'],
'scripts', 'compare-locales')
env = self.query_env(partial_env={'PYTHONPATH':
os.path.join(dirs['abs_compare_locales_dir'],
'lib')})
env = self.query_bootstrap_env()
compare_locales_error_list = list(PythonErrorList)
self.rmtree(dirs['abs_merge_dir'])
self.mkdir_p(dirs['abs_merge_dir'])
command = "python %s -m %s l10n.ini %s %s" % (compare_locales_script,
dirs['abs_merge_dir'], dirs['abs_l10n_dir'], locale)
command = ['compare-locales',
'--merge-dir', dirs['abs_merge_dir'], locale]
self.info("*** BEGIN compare-locales %s" % locale)
status = self.run_command(command, error_list=compare_locales_error_list,
cwd=dirs['abs_locales_src_dir'], env=env,
status = self._mach(command, env, error_list=compare_locales_error_list,
halt_on_failure=halt_on_failure)
self.info("*** END compare-locales %s" % locale)
return status
@ -187,8 +195,6 @@ class LocalesMixin(ChunkingMixin):
'merged')
dirs['abs_locales_dir'] = os.path.join(dirs['abs_objdir'],
c['locales_dir'])
dirs['abs_compare_locales_dir'] = os.path.join(dirs['abs_work_dir'],
'compare-locales')
for key in dirs.keys():
if key not in abs_dirs:
abs_dirs[key] = dirs[key]

View File

@ -606,25 +606,12 @@ class DesktopSingleLocale(LocalesMixin, ReleaseMixin, MockMixin, BuildbotMixin,
self.copyfile(src, dst)
self.read_from_file(dst, verbose=True)
def _mach(self, target, env, halt_on_failure=True, output_parser=None):
dirs = self.query_abs_dirs()
mach = self._get_mach_executable()
return self.run_command(mach + target,
halt_on_failure=True,
env=env,
cwd=dirs['abs_mozilla_dir'],
output_parser=None)
def _mach_configure(self):
"""calls mach configure"""
env = self.query_bootstrap_env()
target = ["configure"]
return self._mach(target=target, env=env)
def _get_mach_executable(self):
python = self.query_exe('python2.7')
return [python, 'mach']
def _get_make_executable(self):
config = self.config
dirs = self.query_abs_dirs()

View File

@ -8271,11 +8271,6 @@
"releaseChannelCollection": "opt-out",
"description": "Number of sessions where at least one chat message was exchanged"
},
"E10S_AUTOSTART": {
"expires_in_version": "never",
"kind": "boolean",
"description": "Whether a session is set to autostart e10s windows"
},
"E10S_AUTOSTART_STATUS": {
"expires_in_version": "never",
"kind": "enumerated",

View File

@ -746,6 +746,11 @@ var Impl = {
// task to complete, but TelemetryStorage blocks on it during shutdown.
TelemetryStorage.runCleanPingArchiveTask();
// Now that FHR/healthreporter is gone, make sure to remove FHR's DB from
// the profile directory. This is a temporary measure that we should drop
// in the future.
TelemetryStorage.removeFHRDatabase();
this._delayedInitTaskDeferred.resolve();
} catch (e) {
this._delayedInitTaskDeferred.reject(e);

View File

@ -20,6 +20,7 @@ Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm", this);
const LOGGER_NAME = "Toolkit.Telemetry";
const LOGGER_PREFIX = "TelemetryStorage::";
@ -413,6 +414,15 @@ this.TelemetryStorage = {
return TelemetryStorageImpl.loadPingFile(aFilePath);
}),
/**
* Remove FHR database files. This is temporary and will be dropped in
* the future.
* @return {Promise} Resolved when the database files are deleted.
*/
removeFHRDatabase: function() {
return TelemetryStorageImpl.removeFHRDatabase();
},
/**
* Only used in tests, builds an archived ping path from the ping metadata.
* @param {String} aPingId The ping id.
@ -1718,6 +1728,42 @@ var TelemetryStorageImpl = {
return true;
},
/**
* Remove FHR database files. This is temporary and will be dropped in
* the future.
* @return {Promise} Resolved when the database files are deleted.
*/
removeFHRDatabase: Task.async(function*() {
this._log.trace("removeFHRDatabase");
// Let's try to remove the FHR DB with the default filename first.
const FHR_DB_DEFAULT_FILENAME = "healthreport.sqlite";
// Even if it's uncommon, there may be 2 additional files: - a "write ahead log"
// (-wal) file and a "shared memory file" (-shm). We need to remove them as well.
let FILES_TO_REMOVE = [
OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_DEFAULT_FILENAME),
OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_DEFAULT_FILENAME + "-wal"),
OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_DEFAULT_FILENAME + "-shm"),
];
// FHR could have used either the default DB file name or a custom one
// through this preference.
const FHR_DB_CUSTOM_FILENAME =
Preferences.get("datareporting.healthreport.dbName", undefined);
if (FHR_DB_CUSTOM_FILENAME) {
FILES_TO_REMOVE.push(
OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_CUSTOM_FILENAME),
OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_CUSTOM_FILENAME + "-wal"),
OS.Path.join(OS.Constants.Path.profileDir, FHR_DB_CUSTOM_FILENAME + "-shm"));
}
for (let f of FILES_TO_REMOVE) {
yield OS.File.remove(f, {ignoreAbsent: true})
.catch(e => this._log.error("removeFHRDatabase - failed to remove " + f, e));
}
}),
};
///// Utility functions

View File

@ -460,6 +460,54 @@ add_task(function* test_telemetryEnabledUnexpectedValue(){
"False must disable Telemetry recording.");
});
add_task(function* test_telemetryCleanFHRDatabase(){
const FHR_DBNAME_PREF = "datareporting.healthreport.dbName";
const CUSTOM_DB_NAME = "unlikely.to.be.used.sqlite";
const DEFAULT_DB_NAME = "healthreport.sqlite";
// Check that we're able to remove a FHR DB with a custom name.
const CUSTOM_DB_PATHS = [
OS.Path.join(OS.Constants.Path.profileDir, CUSTOM_DB_NAME),
OS.Path.join(OS.Constants.Path.profileDir, CUSTOM_DB_NAME + "-wal"),
OS.Path.join(OS.Constants.Path.profileDir, CUSTOM_DB_NAME + "-shm"),
];
Preferences.set(FHR_DBNAME_PREF, CUSTOM_DB_NAME);
// Write fake DB files to the profile directory.
for (let dbFilePath of CUSTOM_DB_PATHS) {
yield OS.File.writeAtomic(dbFilePath, "some data");
}
// Trigger the cleanup and check that the files were removed.
yield TelemetryStorage.removeFHRDatabase();
for (let dbFilePath of CUSTOM_DB_PATHS) {
Assert.ok(!(yield OS.File.exists(dbFilePath)), "The DB must not be on the disk anymore: " + dbFilePath);
}
// We should not break anything if there's no DB file.
yield TelemetryStorage.removeFHRDatabase();
// Check that we're able to remove a FHR DB with the default name.
Preferences.reset(FHR_DBNAME_PREF);
const DEFAULT_DB_PATHS = [
OS.Path.join(OS.Constants.Path.profileDir, DEFAULT_DB_NAME),
OS.Path.join(OS.Constants.Path.profileDir, DEFAULT_DB_NAME + "-wal"),
OS.Path.join(OS.Constants.Path.profileDir, DEFAULT_DB_NAME + "-shm"),
];
// Write fake DB files to the profile directory.
for (let dbFilePath of DEFAULT_DB_PATHS) {
yield OS.File.writeAtomic(dbFilePath, "some data");
}
// Trigger the cleanup and check that the files were removed.
yield TelemetryStorage.removeFHRDatabase();
for (let dbFilePath of DEFAULT_DB_PATHS) {
Assert.ok(!(yield OS.File.exists(dbFilePath)), "The DB must not be on the disk anymore: " + dbFilePath);
}
});
add_task(function* stopServer(){
yield PingServer.stop();
do_test_finished();

View File

@ -4782,7 +4782,6 @@ mozilla::BrowserTabsRemoteAutostart()
status = kE10sEnabledByUser;
}
mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_AUTOSTART, gBrowserTabsRemoteAutostart);
mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_AUTOSTART_STATUS, status);
if (Preferences::GetBool("browser.enabledE10SFromPrompt", false)) {
mozilla::Telemetry::Accumulate(mozilla::Telemetry::E10S_STILL_ACCEPTED_FROM_PROMPT,