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

This commit is contained in:
Ryan VanderMeulen 2014-07-03 23:24:35 -04:00
commit f2eae24eb3
32 changed files with 449 additions and 136 deletions

View File

@ -551,14 +551,15 @@ SocialShare = {
},
update: function() {
let shareButton = this.shareButton;
if (!shareButton)
let widget = CustomizableUI.getWidget("social-share-button");
if (!widget)
return;
// if we got here, the button is in the window somewhere, update it's hidden
// state based on available providers.
shareButton.hidden = !SocialUI.enabled ||
[p for (p of Social.providers) if (p.shareURL)].length == 0;
let disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
let shareButton = widget.forWindow(window).node;
// hidden state is based on available share providers and location of
// button. It's always visible and disabled in the customization palette.
shareButton.hidden = !SocialUI.enabled || (widget.areaType &&
[p for (p of Social.providers) if (p.shareURL)].length == 0);
let disabled = !widget.areaType || shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
// 1. update the relevent command's disabled state so the keyboard
// shortcut only works when available.

View File

@ -939,6 +939,7 @@
tooltiptext="&sharePageCmd.label;"
cui-areatype="toolbar"
removable="true"
hidden="true"
command="Social:SharePage"/>
</hbox>

View File

@ -13,7 +13,6 @@ Cu.import("resource://gre/modules/PageThumbs.jsm");
Cu.import("resource://gre/modules/BackgroundPageThumbs.jsm");
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect",
"resource://gre/modules/Geometry.jsm");

View File

@ -179,19 +179,18 @@ let gTransformation = {
if (!aSite || aSite == gDrag.draggedSite)
return;
let deferred = Promise.defer();
batch.push(deferred.promise);
let cb = deferred.resolve;
if (!cells[aIndex])
// The site disappeared from the grid, hide it.
this.hideSite(aSite, cb);
else if (this._getNodeOpacity(aSite.node) != 1)
// The site disappeared before but is now back, show it.
this.showSite(aSite, cb);
else
// The site's position has changed, move it around.
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
batch.push(new Promise(resolve => {
if (!cells[aIndex]) {
// The site disappeared from the grid, hide it.
this.hideSite(aSite, resolve);
} else if (this._getNodeOpacity(aSite.node) != 1) {
// The site disappeared before but is now back, show it.
this.showSite(aSite, resolve);
} else {
// The site's position has changed, move it around.
this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
}
}));
}, this);
if (callback) {

View File

@ -134,17 +134,16 @@ let gUpdater = {
if (!aSite || aSites.indexOf(aSite) != -1)
return;
let deferred = Promise.defer();
batch.push(deferred.promise);
batch.push(new Promise(resolve => {
// Fade out the to-be-removed site.
gTransformation.hideSite(aSite, function () {
let node = aSite.node;
// Fade out the to-be-removed site.
gTransformation.hideSite(aSite, function () {
let node = aSite.node;
// Remove the site from the DOM.
node.parentNode.removeChild(node);
deferred.resolve();
});
// Remove the site from the DOM.
node.parentNode.removeChild(node);
resolve();
});
}));
});
Promise.all(batch).then(aCallback);
@ -164,19 +163,18 @@ let gUpdater = {
if (aSite || !aLinks[aIndex])
return;
let deferred = Promise.defer();
batch.push(deferred.promise);
batch.push(new Promise(resolve => {
// Create the new site and fade it in.
let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
// Create the new site and fade it in.
let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
// Set the site's initial opacity to zero.
site.node.style.opacity = 0;
// Set the site's initial opacity to zero.
site.node.style.opacity = 0;
// Flush all style changes for the dynamically inserted site to make
// the fade-in transition work.
window.getComputedStyle(site.node).opacity;
gTransformation.showSite(site, function () deferred.resolve());
// Flush all style changes for the dynamically inserted site to make
// the fade-in transition work.
window.getComputedStyle(site.node).opacity;
gTransformation.showSite(site, resolve);
}));
});
Promise.all(batch).then(aCallback);

View File

@ -47,6 +47,7 @@ function runTests() {
expected.action = "unpin";
expected.pinned = true;
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
yield whenPagesUpdated();
// Block the site in the 0th tile spot
let blockedSite = getCell(0).node.querySelector(".newtab-site");

View File

@ -11,7 +11,8 @@ pref("startup.homepage_welcome_url","");
pref("app.update.interval", 28800); // 8 hours
// The time interval between the downloading of mar file chunks in the
// background (in seconds)
pref("app.update.download.backgroundInterval", 60);
// 0 means "download everything at once"
pref("app.update.download.backgroundInterval", 0);
// Give the user x seconds to react before showing the big UI. default=168 hours
pref("app.update.promptWaitTime", 604800);
// URL user can browse to manually if for some reason all update installation

View File

@ -8,7 +8,8 @@ pref("startup.homepage_welcome_url", "https://www.mozilla.org/projects/firefox/%
pref("app.update.interval", 7200); // 2 hours
// The time interval between the downloading of mar file chunks in the
// background (in seconds)
pref("app.update.download.backgroundInterval", 60);
// 0 means "download everything at once"
pref("app.update.download.backgroundInterval", 0);
// Give the user x seconds to react before showing the big UI. default=12 hours
pref("app.update.promptWaitTime", 43200);
// URL user can browse to manually if for some reason all update installation

View File

@ -78,7 +78,7 @@
<!-- Tracking -->
<groupbox id="trackingGroup" data-category="panePrivacy" hidden="true" align="start">
<caption><label>&tracking.label;</label></caption>
<radiogroup id="doNotTrackSelection" orient="vertical"
<radiogroup id="doNotTrackSelection" orient="vertical" align="start"
preference="privacy.donottrackheader.value"
onsynctopreference="return gPrivacyPane.setTrackingPrefs()"
onsyncfrompreference="return gPrivacyPane.getTrackingPrefs()">
@ -151,37 +151,31 @@
<vbox id="historyCustomPane">
<separator class="thin"/>
<vbox class="indent">
<hbox>
<vbox align="start">
<checkbox id="privateBrowsingAutoStart"
label="&privateBrowsingPermanent2.label;"
accesskey="&privateBrowsingPermanent2.accesskey;"
preference="browser.privatebrowsing.autostart"
oncommand="gPrivacyPane.updateAutostart()"/>
<spacer flex="1"/>
</hbox>
</vbox>
<vbox class="indent">
<hbox>
<vbox align="start">
<checkbox id="rememberHistory"
label="&rememberHistory2.label;"
accesskey="&rememberHistory2.accesskey;"
preference="places.history.enabled"/>
<spacer flex="1"/>
</hbox>
<hbox>
<checkbox id="rememberForms"
label="&rememberSearchForm.label;"
accesskey="&rememberSearchForm.accesskey;"
preference="browser.formfill.enable"/>
<spacer flex="1"/>
</hbox>
</vbox>
<hbox id="cookiesBox">
<checkbox id="acceptCookies" label="&acceptCookies.label;"
preference="network.cookie.cookieBehavior"
accesskey="&acceptCookies.accesskey;"
onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
<spacer flex="1"/>
<spacer flex="1" />
<button id="cookieExceptions" oncommand="gPrivacyPane.showCookieExceptions();"
label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
preference="pref.privacy.disable_button.cookie_exceptions"/>

View File

@ -3618,6 +3618,7 @@ notification[value="translation"] {
border-bottom: 1px solid #c4c4c4;
padding-top: 1px;
padding-bottom: 1px;
min-height: 35px;
}
.translate-infobar-element {

View File

@ -10,6 +10,11 @@ import android.content.Context;
public class PrivateTab extends Tab {
public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
super(context, id, url, external, parentId, title);
// Init background to background_private to ensure flicker-free
// private tab creation. Page loads will reset it to white as expected.
final int bgColor = context.getResources().getColor(R.color.background_private);
setBackgroundColor(bgColor);
}
@Override

View File

@ -136,10 +136,8 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
public LayerRenderer(LayerView view) {
mView = view;
try {
mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal);
} catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
setOverscrollColor(R.color.background_normal);
Bitmap scrollbarImage = view.getScrollbarImage();
IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
@ -199,6 +197,12 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
activateDefaultProgram();
}
void setOverscrollColor(int colorId) {
try {
mOverscrollColor = mView.getContext().getResources().getColor(colorId);
} catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
}
public void createDefaultProgram() {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER);
@ -717,6 +721,10 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
// thread, so this may need to be changed if any problems appear.
if (msg == Tabs.TabEvents.SELECTED) {
if (mView != null) {
final int overscrollColor =
(tab.isPrivate() ? R.color.background_private : R.color.background_normal);
setOverscrollColor(overscrollColor);
if (mView.getChildAt(0) != null) {
mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor());
}

View File

@ -3,11 +3,7 @@
- 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/. -->
<org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:id="@+id/remote_tabs_setup_containing_layout"
style="@style/TabsPanelFrame"
@ -56,4 +52,4 @@
</LinearLayout>
</org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel>
</merge>

View File

@ -3,11 +3,7 @@
- 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/. -->
<org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:id="@+id/remote_tabs_verification_containing_layout"
style="@style/TabsPanelFrame"
@ -51,4 +47,4 @@
</LinearLayout>
</org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel>
</merge>

View File

@ -99,25 +99,25 @@ class RemoteTabsPanel extends FrameLayout implements PanelView {
}
private PanelView inflatePanel(final RemotePanelType panelType) {
final LayoutInflater inflater = LayoutInflater.from(getContext());
final View inflatedView;
final PanelView view;
switch (panelType) {
case SETUP:
inflatedView = inflater.inflate(R.layout.remote_tabs_setup_panel, null);
view = new RemoteTabsSetupPanel(getContext());
break;
case VERIFICATION:
inflatedView = inflater.inflate(R.layout.remote_tabs_verification_panel, null);
view = new RemoteTabsVerificationPanel(getContext());
break;
case CONTAINER:
inflatedView = inflater.inflate(R.layout.remote_tabs_container_panel, null);
final LayoutInflater inflater = LayoutInflater.from(getContext());
view = (PanelView) inflater.inflate(R.layout.remote_tabs_container_panel, null);
break;
default:
throw new IllegalArgumentException("Unknown panelType, " + panelType);
}
return (PanelView) inflatedView;
return view;
}
}

View File

@ -14,7 +14,7 @@ import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
import android.content.Context;
import android.content.Intent;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@ -25,18 +25,14 @@ import android.widget.ScrollView;
* contained by the {@link RemoteTabsPanel}.
*/
class RemoteTabsSetupPanel extends ScrollView implements PanelView {
private LinearLayout containingLayout;
private final LinearLayout containingLayout;
private TabsPanel tabsPanel;
public RemoteTabsSetupPanel(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
public RemoteTabsSetupPanel(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.remote_tabs_setup_panel, this);
containingLayout = (LinearLayout) findViewById(R.id.remote_tabs_setup_containing_layout);
final View setupGetStartedButton =

View File

@ -10,8 +10,8 @@ import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.tabspanel.TabsPanel.PanelView;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ScrollView;
@ -25,18 +25,14 @@ import android.widget.TextView;
class RemoteTabsVerificationPanel extends ScrollView implements PanelView {
private static final String LOG_TAG = RemoteTabsVerificationPanel.class.getSimpleName();
private LinearLayout containingLayout;
private final LinearLayout containingLayout;
private TabsPanel tabsPanel;
public RemoteTabsVerificationPanel(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
public RemoteTabsVerificationPanel(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.remote_tabs_verification_panel, this);
containingLayout = (LinearLayout) findViewById(R.id.remote_tabs_verification_containing_layout);
final View resendLink = containingLayout.findViewById(R.id.remote_tabs_confirm_resend);

View File

@ -172,9 +172,15 @@ public class TabsPanel extends LinearLayout
@Override
public void onClick(View view) {
final Menu menu = mPopupMenu.getMenu();
// Each panel has a "+" shortcut button, so don't show it for that panel.
menu.findItem(R.id.new_tab).setVisible(mCurrentPanel != Panel.NORMAL_TABS);
menu.findItem(R.id.new_private_tab).setVisible(mCurrentPanel != Panel.PRIVATE_TABS);
// Only show "Clear * tabs" for current panel.
menu.findItem(R.id.close_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
mPopupMenu.show();
}
});

View File

@ -1073,6 +1073,35 @@ org.mozilla.crashes.crashes
This measurement contains a historical record of application crashes.
Version 4
^^^^^^^^^
This version follows up from version 3, adding submissions which are now
tracked by the :ref:`crashes_crashmanager`.
This measurement will be reported on each day there was a crash or crash
submission. Records may contain the following fields, whose values indicate
the number of crashes, hangs, or submissions that occurred on the given day:
* main-crash
* main-crash-submission-succeeded
* main-crash-submission-failed
* main-hang
* main-hang-submission-succeeded
* main-hang-submission-failed
* content-crash
* content-crash-submission-succeeded
* content-crash-submission-failed
* content-hang
* content-hang-submission-succeeded
* content-hang-submission-failed
* plugin-crash
* plugin-crash-submission-succeeded
* plugin-crash-submission-failed
* plugin-hang
* plugin-hang-submission-succeeded
* plugin-hang-submission-failed
Version 3
^^^^^^^^^
@ -1152,6 +1181,14 @@ Example
"_v": 2,
"mainCrash": 2
}
"org.mozilla.crashes.crashes": {
"_v": 4,
"main-crash": 2,
"main-crash-submission-succeeded": 1,
"main-crash-submission-failed": 1,
"main-hang": 1,
"plugin-crash": 2
}
org.mozilla.healthreport.submissions
------------------------------------

View File

@ -1047,6 +1047,38 @@ DailyCrashesMeasurement3.prototype = Object.freeze({
},
});
function DailyCrashesMeasurement4() {
Metrics.Measurement.call(this);
}
DailyCrashesMeasurement4.prototype = Object.freeze({
__proto__: Metrics.Measurement.prototype,
name: "crashes",
version: 4,
fields: {
"main-crash": DAILY_LAST_NUMERIC_FIELD,
"main-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
"main-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
"main-hang": DAILY_LAST_NUMERIC_FIELD,
"main-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
"main-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
"content-crash": DAILY_LAST_NUMERIC_FIELD,
"content-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
"content-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
"content-hang": DAILY_LAST_NUMERIC_FIELD,
"content-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
"content-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
"plugin-crash": DAILY_LAST_NUMERIC_FIELD,
"plugin-crash-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
"plugin-crash-submission-failed": DAILY_LAST_NUMERIC_FIELD,
"plugin-hang": DAILY_LAST_NUMERIC_FIELD,
"plugin-hang-submission-succeeded": DAILY_LAST_NUMERIC_FIELD,
"plugin-hang-submission-failed": DAILY_LAST_NUMERIC_FIELD,
},
});
this.CrashesProvider = function () {
Metrics.Provider.call(this);
@ -1063,6 +1095,7 @@ CrashesProvider.prototype = Object.freeze({
DailyCrashesMeasurement1,
DailyCrashesMeasurement2,
DailyCrashesMeasurement3,
DailyCrashesMeasurement4,
],
pullOnly: true,
@ -1075,8 +1108,8 @@ CrashesProvider.prototype = Object.freeze({
this._log.info("Grabbing crash counts from crash manager.");
let crashCounts = yield this._manager.getCrashCountsByDay();
let m = this.getMeasurement("crashes", 3);
let fields = DailyCrashesMeasurement3.prototype.fields;
let m = this.getMeasurement("crashes", 4);
let fields = DailyCrashesMeasurement4.prototype.fields;
for (let [day, types] of crashCounts) {
let date = Metrics.daysToDate(day);

View File

@ -50,9 +50,17 @@ add_task(function* test_collect() {
yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH,
"mc1", day1);
yield manager.addSubmission(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH,
true,
"mc1", day1)
yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH,
"mc2", day1);
yield manager.addSubmission(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH,
false,
"mc2", day1)
yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
manager.CRASH_TYPE_HANG,
"ch", day1);
@ -66,13 +74,17 @@ add_task(function* test_collect() {
yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
manager.CRASH_TYPE_CRASH,
"cc", day2);
yield manager.addSubmission(manager.PROCESS_TYPE_CONTENT,
manager.CRASH_TYPE_CRASH,
true,
"cc", day2)
yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN,
manager.CRASH_TYPE_HANG,
"ph", day2);
yield provider.collectDailyData();
let m = provider.getMeasurement("crashes", 3);
let m = provider.getMeasurement("crashes", 4);
let values = yield m.getValues();
do_check_eq(values.days.size, 2);
do_check_true(values.days.hasDay(day1));
@ -81,6 +93,10 @@ add_task(function* test_collect() {
let value = values.days.getDay(day1);
do_check_true(value.has("main-crash"));
do_check_eq(value.get("main-crash"), 2);
do_check_true(value.has("main-crash-submission-succeeded"));
do_check_eq(value.get("main-crash-submission-succeeded"), 1);
do_check_true(value.has("main-crash-submission-failed"));
do_check_eq(value.get("main-crash-submission-failed"), 1);
do_check_true(value.has("content-hang"));
do_check_eq(value.get("content-hang"), 1);
do_check_true(value.has("plugin-crash"));
@ -91,6 +107,8 @@ add_task(function* test_collect() {
do_check_eq(value.get("main-hang"), 1);
do_check_true(value.has("content-crash"));
do_check_eq(value.get("content-crash"), 1);
do_check_true(value.has("content-crash-submission-succeeded"));
do_check_eq(value.get("content-crash-submission-succeeded"), 1);
do_check_true(value.has("plugin-hang"));
do_check_eq(value.get("plugin-hang"), 1);

View File

@ -130,12 +130,21 @@ this.CrashManager.prototype = Object.freeze({
// A crash in a plugin process.
PROCESS_TYPE_PLUGIN: "plugin",
// A submission of a crash.
PROCESS_TYPE_SUBMISSION: "submission",
// A real crash.
CRASH_TYPE_CRASH: "crash",
// A hang.
CRASH_TYPE_HANG: "hang",
// A successful submission.
SUBMISSION_TYPE_SUCCEEDED: "succeeded",
// A failed submission.
SUBMISSION_TYPE_FAILED: "failed",
DUMP_REGEX: /^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.dmp$/i,
SUBMITTED_REGEX: /^bp-(?:hr-)?([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.txt$/i,
ALL_REGEX: /^(.*)$/,
@ -360,6 +369,38 @@ this.CrashManager.prototype = Object.freeze({
}.bind(this));
},
/**
* Record the occurrence of a crash submission.
*
* @param processType (string) One of the PROCESS_TYPE constants.
* @param crashType (string) One of the CRASH_TYPE constants.
* @param succeeded (boolean) Whether the submission succeeded.
* @param id (string) Crash ID. Likely a UUID.
* @param date (Date) When the crash occurred.
*
* @return boolean True if the crash submission was recorded and false if not.
*/
addSubmission: function (processType, crashType, succeeded, id, date) {
return Task.spawn(function* () {
let store = yield this._getStore();
if (this._addSubmissionAsCrash(store, processType, crashType, succeeded,
id, date)) {
yield store.save();
}
}.bind(this));
},
_addSubmissionAsCrash: function (store, processType, crashType, succeeded,
id, date) {
let id = id + "-" + this.PROCESS_TYPE_SUBMISSION;
let process = processType + "-" + crashType + "-" +
this.PROCESS_TYPE_SUBMISSION;
let submission_type = (
succeeded ? this.SUBMISSION_TYPE_SUCCEEDED : this.SUBMISSION_TYPE_FAILED);
return store.addCrash(process, submission_type, id, date);
},
/**
* Obtain the paths of all unprocessed events files.
*
@ -425,27 +466,35 @@ this.CrashManager.prototype = Object.freeze({
// The payload types and formats are documented in docs/crash-events.rst.
// Do not change the format of an existing type. Instead, invent a new
// type.
// DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
let lines = payload.split("\n");
// type in event file => [processType, crashType]
let eventMap = {
"crash.main.1": ["main", "crash"],
};
switch (type) {
case "crash.main.1":
if (lines.length > 1) {
this._log.warn("Multiple lines unexpected in payload for " +
entry.path);
return this.EVENT_FILE_ERROR_MALFORMED;
}
store.addCrash(this.PROCESS_TYPE_MAIN, this.CRASH_TYPE_CRASH,
payload, date);
break;
if (type in eventMap) {
let lines = payload.split("\n");
if (lines.length > 1) {
this._log.warn("Multiple lines unexpected in payload for " +
entry.path);
return this.EVENT_FILE_ERROR_MALFORMED;
}
case "crash.submission.1":
if (lines.length == 3) {
this._addSubmissionAsCrash(store, this.PROCESS_TYPE_MAIN,
this.CRASH_TYPE_CRASH,
lines[1] === "true", lines[0], date);
} else {
return this.EVENT_FILE_ERROR_MALFORMED;
}
break;
store.addCrash(...eventMap[type], payload, date);
return this.EVENT_FILE_SUCCESS;
default:
return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
}
// DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
return this.EVENT_FILE_SUCCESS;
},
/**

View File

@ -74,6 +74,18 @@ The payload of this event is the string crash ID, very likely a UUID.
There should be ``UUID.dmp`` and ``UUID.extra`` files on disk, saved by
Breakpad.
crash.submission.1
^^^^^^^^^^^^
This event is produced when a crash is submitted.
The payload of this event is delimited by UNIX newlines (*\n*) and contains the
following fields:
* The crash ID string
* "true" if the submission succeeded or "false" otherwise
* The remote crash ID string if the submission succeeded
Aggregated Event Log
====================

View File

@ -304,3 +304,42 @@ add_task(function* test_addCrash() {
Assert.equal(crash.type, m.PROCESS_TYPE_PLUGIN + "-" + m.CRASH_TYPE_HANG);
Assert.ok(crash.isOfType(m.PROCESS_TYPE_PLUGIN, m.CRASH_TYPE_HANG));
});
add_task(function* test_addSubmission() {
let m = yield getManager();
let crashes = yield m.getCrashes();
Assert.equal(crashes.length, 0);
yield m.addSubmission(m.PROCESS_TYPE_MAIN, m.CRASH_TYPE_CRASH, true,
"success", DUMMY_DATE);
yield m.addSubmission(m.PROCESS_TYPE_MAIN, m.CRASH_TYPE_CRASH, false,
"failure", DUMMY_DATE);
crashes = yield m.getCrashes();
Assert.equal(crashes.length, 2);
let map = new Map(crashes.map(crash => [crash.id, crash]));
let crash = map.get("success-submission");
Assert.ok(!!crash);
Assert.equal(crash.crashDate, DUMMY_DATE);
Assert.equal(crash.type,
m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
m.PROCESS_TYPE_SUBMISSION + "-" + m.SUBMISSION_TYPE_SUCCEEDED);
Assert.ok(
crash.isOfType(m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
m.PROCESS_TYPE_SUBMISSION, m.SUBMISSION_TYPE_SUCCEEDED));
let crash = map.get("failure-submission");
Assert.ok(!!crash);
Assert.equal(crash.crashDate, DUMMY_DATE);
Assert.equal(crash.type,
m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
m.PROCESS_TYPE_SUBMISSION + "-" + m.SUBMISSION_TYPE_FAILED);
Assert.ok(
crash.isOfType(m.PROCESS_TYPE_MAIN + "-" + m.CRASH_TYPE_CRASH + "-" +
m.PROCESS_TYPE_SUBMISSION, m.SUBMISSION_TYPE_FAILED));
});

View File

@ -17,8 +17,11 @@ const {
PROCESS_TYPE_MAIN,
PROCESS_TYPE_CONTENT,
PROCESS_TYPE_PLUGIN,
PROCESS_TYPE_SUBMISSION,
CRASH_TYPE_CRASH,
CRASH_TYPE_HANG,
SUBMISSION_TYPE_SUCCEEDED,
SUBMISSION_TYPE_FAILED,
} = CrashManager.prototype;
const CrashStore = bsp.CrashStore;
@ -273,6 +276,44 @@ add_task(function* test_add_plugin_hang() {
Assert.equal(crashes.length, 2);
});
add_task(function* test_add_submission() {
let s = yield getStore();
Assert.ok(
s.addCrash(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_SUCCEEDED,
"id1", new Date())
);
Assert.equal(s.crashesCount, 1);
let c = s.crashes[0];
Assert.ok(c.crashDate);
Assert.equal(c.type, PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
PROCESS_TYPE_SUBMISSION + "-" + SUBMISSION_TYPE_SUCCEEDED);
Assert.ok(c.isOfType(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_SUCCEEDED));
Assert.ok(
s.addCrash(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_FAILED,
"id2", new Date())
);
Assert.equal(s.crashesCount, 2);
// Duplicate.
Assert.ok(
s.addCrash(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH + "-" +
PROCESS_TYPE_SUBMISSION, SUBMISSION_TYPE_SUCCEEDED,
"id1", new Date())
);
Assert.equal(s.crashesCount, 2);
let crashes = s.getCrashesOfType(PROCESS_TYPE_MAIN + "-" + CRASH_TYPE_CRASH +
"-" + PROCESS_TYPE_SUBMISSION,
SUBMISSION_TYPE_SUCCEEDED);
Assert.equal(crashes.length, 1);
});
add_task(function* test_add_mixed_types() {
let s = yield getStore();

View File

@ -32,9 +32,12 @@ namespace CrashReporter {
StringTable gStrings;
string gSettingsPath;
string gEventsPath;
int gArgc;
char** gArgv;
enum SubmissionResult {Succeeded, Failed};
static auto_ptr<ofstream> gLogStream(nullptr);
static string gReporterDumpFile;
static string gExtraFile;
@ -172,6 +175,53 @@ bool WriteStringsToFile(const string& path,
return success;
}
static string Basename(const string& file)
{
string::size_type slashIndex = file.rfind(UI_DIR_SEPARATOR);
if (slashIndex != string::npos)
return file.substr(slashIndex + 1);
else
return file;
}
static string GetDumpLocalID()
{
string localId = Basename(gReporterDumpFile);
string::size_type dot = localId.rfind('.');
if (dot == string::npos)
return "";
return localId.substr(0, dot);
}
static void WriteSubmissionEvent(SubmissionResult result,
const string& remoteId)
{
if (gEventsPath.empty()) {
// If there is no path for writing the submission event, skip it.
return;
}
string localId = GetDumpLocalID();
string fpath = gEventsPath + UI_DIR_SEPARATOR + localId + "-submission";
ofstream* f = UIOpenWrite(fpath.c_str());
time_t tm;
time(&tm);
if (f->is_open()) {
*f << "crash.submission.1\n";
*f << tm << "\n";
*f << localId << "\n";
*f << (result == Succeeded ? "true" : "false") << "\n";
*f << remoteId;
f->close();
}
delete f;
}
void LogMessage(const std::string& message)
{
if (gLogStream.get()) {
@ -218,15 +268,6 @@ static string GetExtraDataFilename(const string& dumpfile)
return filename;
}
static string Basename(const string& file)
{
int slashIndex = file.rfind(UI_DIR_SEPARATOR);
if (slashIndex >= 0)
return file.substr(slashIndex + 1);
else
return file;
}
static bool MoveCrashData(const string& toDir,
string& dumpfile,
string& extrafile)
@ -316,6 +357,7 @@ static bool AddSubmittedReport(const string& serverResponse)
file->close();
delete file;
WriteSubmissionEvent(Succeeded, responseItems["CrashID"]);
return true;
}
@ -343,7 +385,10 @@ void SendCompleted(bool success, const string& serverResponse)
return;
directory.resize(slashpos);
UIPruneSavedDumps(directory);
WriteSubmissionEvent(Failed, "");
}
} else {
WriteSubmissionEvent(Failed, "");
}
}
@ -514,6 +559,23 @@ int main(int argc, char** argv)
OpenLogFile();
#ifdef XP_WIN32
static const wchar_t kEventsDirKey[] = L"MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
const wchar_t *eventsPath = _wgetenv(kEventsDirKey);
if (eventsPath && *eventsPath) {
gEventsPath = WideToUTF8(eventsPath);
}
#else
static const char kEventsDirKey[] = "MOZ_CRASHREPORTER_EVENTS_DIRECTORY";
const char *eventsPath = getenv(kEventsDirKey);
if (eventsPath && *eventsPath) {
gEventsPath = eventsPath;
}
#endif
else {
gEventsPath.clear();
}
if (!UIFileExists(gReporterDumpFile)) {
UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
return 0;

View File

@ -82,6 +82,7 @@ typedef std::map<std::string, std::string> StringTable;
namespace CrashReporter {
extern StringTable gStrings;
extern std::string gSettingsPath;
extern std::string gEventsPath;
extern int gArgc;
extern char** gArgv;

View File

@ -163,6 +163,7 @@ static XP_CHAR* crashReporterPath;
// Where crash events should go.
static XP_CHAR* eventsDirectory;
static char* eventsEnv = nullptr;
// If this is false, we don't launch the crash reporter
static bool doReport = true;
@ -2107,10 +2108,29 @@ SetCrashEventsDir(nsIFile* aDir)
nsString path;
eventsDir->GetPath(path);
eventsDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(path));
// Save the path in the environment for the crash reporter application.
nsAutoString eventsDirEnv(NS_LITERAL_STRING("MOZ_CRASHREPORTER_EVENTS_DIRECTORY="));
eventsDirEnv.Append(path);
_wputenv(eventsDirEnv.get());
#else
nsCString path;
eventsDir->GetNativePath(path);
eventsDirectory = ToNewCString(path);
// Save the path in the environment for the crash reporter application.
nsAutoCString eventsDirEnv("MOZ_CRASHREPORTER_EVENTS_DIRECTORY=");
eventsDirEnv.Append(path);
// PR_SetEnv() wants the string to be available for the lifetime
// of the app, so dup it here.
char* oldEventsEnv = eventsEnv;
eventsEnv = ToNewCString(eventsDirEnv);
PR_SetEnv(eventsEnv);
if (oldEventsEnv) {
NS_Free(oldEventsEnv);
}
#endif
}

View File

@ -16,7 +16,6 @@
<script type="application/javascript"
src="utils.js"/>
<script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script>
<script type="application/javascript">
<![CDATA[

View File

@ -353,6 +353,7 @@ notification > button > .button-box > .button-text {
@media (min-resolution: 2dppx) {
.close-icon > .button-icon,
.close-icon > .button-box > .button-icon,
.close-icon > .toolbarbutton-icon {
width: 16px;
}

View File

@ -6,6 +6,7 @@
* the code you put here can be evaluated by both! */
Cu.import("resource://webapprt/modules/WebappRT.jsm");
Cu.import("resource://gre/modules/Task.jsm");
// When WebappsHandler opens an install confirmation dialog for apps we install,
// close it, which will be seen as the equivalent of cancelling the install.
@ -42,7 +43,7 @@ Services.ww.registerNotification({
* The callback to call once the transmogrification is complete.
*/
function becomeWebapp(manifestURL, parameters, onBecome) {
function observeInstall(subj, topic, data) {
let observeInstall = Task.async(function*(subj, topic, data) {
Services.obs.removeObserver(observeInstall, "webapps-ask-install");
// Step 2: Configure the runtime session to represent the app.
@ -52,7 +53,7 @@ function becomeWebapp(manifestURL, parameters, onBecome) {
let scope = {};
Cu.import("resource://gre/modules/Webapps.jsm", scope);
Cu.import("resource://webapprt/modules/Startup.jsm", scope);
scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
yield scope.DOMApplicationRegistry.confirmInstall(JSON.parse(data));
let installRecord = JSON.parse(data);
installRecord.mm = subj;
@ -68,20 +69,20 @@ function becomeWebapp(manifestURL, parameters, onBecome) {
null);
}
let promise = scope.startup(win);
// During chrome tests, we use the same window to load all the tests. We
// need to change the buildID so that the permissions for the currently
// tested application get installed.
Services.prefs.setCharPref("webapprt.buildID", WebappRT.config.app.manifestURL);
// During tests, the webapps registry is already loaded.
// The Startup module needs to be notified when the webapps registry
// gets loaded, so we do that now.
// During tests, the webapps registry is already loaded,
// but SystemMessageInternal expects to be notified when the registry
// start and then when it's ready, so we do that now.
Services.obs.notifyObservers(this, "webapps-registry-start", null);
Services.obs.notifyObservers(this, "webapps-registry-ready", null);
promise.then(onBecome);
}
yield scope.startup(win);
onBecome();
});
Services.obs.addObserver(observeInstall, "webapps-ask-install", false);
// Step 1: Install the app at the URL specified by the manifest.

View File

@ -6,7 +6,8 @@
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window windowtype="webapprt:mochitest"
<window id="browserTestHarness"
windowtype="webapprt:mochitest"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://webapprt/content/mochitest.js"/>