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

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

View File

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

View File

@ -939,6 +939,7 @@
tooltiptext="&sharePageCmd.label;" tooltiptext="&sharePageCmd.label;"
cui-areatype="toolbar" cui-areatype="toolbar"
removable="true" removable="true"
hidden="true"
command="Social:SharePage"/> command="Social:SharePage"/>
</hbox> </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/BackgroundPageThumbs.jsm");
Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm"); Cu.import("resource://gre/modules/DirectoryLinksProvider.jsm");
Cu.import("resource://gre/modules/NewTabUtils.jsm"); Cu.import("resource://gre/modules/NewTabUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Rect", XPCOMUtils.defineLazyModuleGetter(this, "Rect",
"resource://gre/modules/Geometry.jsm"); "resource://gre/modules/Geometry.jsm");

View File

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

View File

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

View File

@ -47,6 +47,7 @@ function runTests() {
expected.action = "unpin"; expected.action = "unpin";
expected.pinned = true; expected.pinned = true;
yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow()); yield EventUtils.synthesizeMouseAtCenter(pinButton, {}, getContentWindow());
yield whenPagesUpdated();
// Block the site in the 0th tile spot // Block the site in the 0th tile spot
let blockedSite = getCell(0).node.querySelector(".newtab-site"); 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 pref("app.update.interval", 28800); // 8 hours
// The time interval between the downloading of mar file chunks in the // The time interval between the downloading of mar file chunks in the
// background (in seconds) // 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 // Give the user x seconds to react before showing the big UI. default=168 hours
pref("app.update.promptWaitTime", 604800); pref("app.update.promptWaitTime", 604800);
// URL user can browse to manually if for some reason all update installation // 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 pref("app.update.interval", 7200); // 2 hours
// The time interval between the downloading of mar file chunks in the // The time interval between the downloading of mar file chunks in the
// background (in seconds) // 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 // Give the user x seconds to react before showing the big UI. default=12 hours
pref("app.update.promptWaitTime", 43200); pref("app.update.promptWaitTime", 43200);
// URL user can browse to manually if for some reason all update installation // URL user can browse to manually if for some reason all update installation

View File

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

View File

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

View File

@ -10,6 +10,11 @@ import android.content.Context;
public class PrivateTab extends Tab { public class PrivateTab extends Tab {
public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) { public PrivateTab(Context context, int id, String url, boolean external, int parentId, String title) {
super(context, id, url, external, parentId, 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 @Override

View File

@ -136,10 +136,8 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
public LayerRenderer(LayerView view) { public LayerRenderer(LayerView view) {
mView = view; mView = view;
try { setOverscrollColor(R.color.background_normal);
mOverscrollColor = view.getContext().getResources().getColor(R.color.background_normal);
} catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
Bitmap scrollbarImage = view.getScrollbarImage(); Bitmap scrollbarImage = view.getScrollbarImage();
IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight()); IntSize size = new IntSize(scrollbarImage.getWidth(), scrollbarImage.getHeight());
scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size); scrollbarImage = expandCanvasToPowerOfTwo(scrollbarImage, size);
@ -199,6 +197,12 @@ public class LayerRenderer implements Tabs.OnTabsChangedListener {
activateDefaultProgram(); activateDefaultProgram();
} }
void setOverscrollColor(int colorId) {
try {
mOverscrollColor = mView.getContext().getResources().getColor(colorId);
} catch (Resources.NotFoundException nfe) { mOverscrollColor = Color.BLACK; }
}
public void createDefaultProgram() { public void createDefaultProgram() {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER); int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_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. // thread, so this may need to be changed if any problems appear.
if (msg == Tabs.TabEvents.SELECTED) { if (msg == Tabs.TabEvents.SELECTED) {
if (mView != null) { if (mView != null) {
final int overscrollColor =
(tab.isPrivate() ? R.color.background_private : R.color.background_normal);
setOverscrollColor(overscrollColor);
if (mView.getChildAt(0) != null) { if (mView.getChildAt(0) != null) {
mView.getChildAt(0).setBackgroundColor(tab.getBackgroundColor()); 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 - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<org.mozilla.gecko.tabspanel.RemoteTabsSetupPanel <merge xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout android:id="@+id/remote_tabs_setup_containing_layout" <LinearLayout android:id="@+id/remote_tabs_setup_containing_layout"
style="@style/TabsPanelFrame" style="@style/TabsPanelFrame"
@ -56,4 +52,4 @@
</LinearLayout> </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 - 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/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<org.mozilla.gecko.tabspanel.RemoteTabsVerificationPanel <merge xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<LinearLayout android:id="@+id/remote_tabs_verification_containing_layout" <LinearLayout android:id="@+id/remote_tabs_verification_containing_layout"
style="@style/TabsPanelFrame" style="@style/TabsPanelFrame"
@ -51,4 +47,4 @@
</LinearLayout> </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) { private PanelView inflatePanel(final RemotePanelType panelType) {
final LayoutInflater inflater = LayoutInflater.from(getContext()); final PanelView view;
final View inflatedView;
switch (panelType) { switch (panelType) {
case SETUP: case SETUP:
inflatedView = inflater.inflate(R.layout.remote_tabs_setup_panel, null); view = new RemoteTabsSetupPanel(getContext());
break; break;
case VERIFICATION: case VERIFICATION:
inflatedView = inflater.inflate(R.layout.remote_tabs_verification_panel, null); view = new RemoteTabsVerificationPanel(getContext());
break; break;
case CONTAINER: 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; break;
default: default:
throw new IllegalArgumentException("Unknown panelType, " + panelType); 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.Context;
import android.content.Intent; import android.content.Intent;
import android.util.AttributeSet; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.ScrollView; import android.widget.ScrollView;
@ -25,18 +25,14 @@ import android.widget.ScrollView;
* contained by the {@link RemoteTabsPanel}. * contained by the {@link RemoteTabsPanel}.
*/ */
class RemoteTabsSetupPanel extends ScrollView implements PanelView { class RemoteTabsSetupPanel extends ScrollView implements PanelView {
private LinearLayout containingLayout; private final LinearLayout containingLayout;
private TabsPanel tabsPanel; private TabsPanel tabsPanel;
public RemoteTabsSetupPanel(Context context, AttributeSet attrs) { public RemoteTabsSetupPanel(Context context) {
super(context, attrs); super(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
LayoutInflater.from(context).inflate(R.layout.remote_tabs_setup_panel, this);
containingLayout = (LinearLayout) findViewById(R.id.remote_tabs_setup_containing_layout); containingLayout = (LinearLayout) findViewById(R.id.remote_tabs_setup_containing_layout);
final View setupGetStartedButton = final View setupGetStartedButton =

View File

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

View File

@ -172,9 +172,15 @@ public class TabsPanel extends LinearLayout
@Override @Override
public void onClick(View view) { public void onClick(View view) {
final Menu menu = mPopupMenu.getMenu(); 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_all_tabs).setVisible(mCurrentPanel == Panel.NORMAL_TABS);
menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS); menu.findItem(R.id.close_private_tabs).setVisible(mCurrentPanel == Panel.PRIVATE_TABS);
mPopupMenu.show(); mPopupMenu.show();
} }
}); });

View File

@ -1073,6 +1073,35 @@ org.mozilla.crashes.crashes
This measurement contains a historical record of application 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 Version 3
^^^^^^^^^ ^^^^^^^^^
@ -1152,6 +1181,14 @@ Example
"_v": 2, "_v": 2,
"mainCrash": 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 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 () { this.CrashesProvider = function () {
Metrics.Provider.call(this); Metrics.Provider.call(this);
@ -1063,6 +1095,7 @@ CrashesProvider.prototype = Object.freeze({
DailyCrashesMeasurement1, DailyCrashesMeasurement1,
DailyCrashesMeasurement2, DailyCrashesMeasurement2,
DailyCrashesMeasurement3, DailyCrashesMeasurement3,
DailyCrashesMeasurement4,
], ],
pullOnly: true, pullOnly: true,
@ -1075,8 +1108,8 @@ CrashesProvider.prototype = Object.freeze({
this._log.info("Grabbing crash counts from crash manager."); this._log.info("Grabbing crash counts from crash manager.");
let crashCounts = yield this._manager.getCrashCountsByDay(); let crashCounts = yield this._manager.getCrashCountsByDay();
let m = this.getMeasurement("crashes", 3); let m = this.getMeasurement("crashes", 4);
let fields = DailyCrashesMeasurement3.prototype.fields; let fields = DailyCrashesMeasurement4.prototype.fields;
for (let [day, types] of crashCounts) { for (let [day, types] of crashCounts) {
let date = Metrics.daysToDate(day); let date = Metrics.daysToDate(day);

View File

@ -50,9 +50,17 @@ add_task(function* test_collect() {
yield manager.addCrash(manager.PROCESS_TYPE_MAIN, yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH, manager.CRASH_TYPE_CRASH,
"mc1", day1); "mc1", day1);
yield manager.addSubmission(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH,
true,
"mc1", day1)
yield manager.addCrash(manager.PROCESS_TYPE_MAIN, yield manager.addCrash(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH, manager.CRASH_TYPE_CRASH,
"mc2", day1); "mc2", day1);
yield manager.addSubmission(manager.PROCESS_TYPE_MAIN,
manager.CRASH_TYPE_CRASH,
false,
"mc2", day1)
yield manager.addCrash(manager.PROCESS_TYPE_CONTENT, yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
manager.CRASH_TYPE_HANG, manager.CRASH_TYPE_HANG,
"ch", day1); "ch", day1);
@ -66,13 +74,17 @@ add_task(function* test_collect() {
yield manager.addCrash(manager.PROCESS_TYPE_CONTENT, yield manager.addCrash(manager.PROCESS_TYPE_CONTENT,
manager.CRASH_TYPE_CRASH, manager.CRASH_TYPE_CRASH,
"cc", day2); "cc", day2);
yield manager.addSubmission(manager.PROCESS_TYPE_CONTENT,
manager.CRASH_TYPE_CRASH,
true,
"cc", day2)
yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN, yield manager.addCrash(manager.PROCESS_TYPE_PLUGIN,
manager.CRASH_TYPE_HANG, manager.CRASH_TYPE_HANG,
"ph", day2); "ph", day2);
yield provider.collectDailyData(); yield provider.collectDailyData();
let m = provider.getMeasurement("crashes", 3); let m = provider.getMeasurement("crashes", 4);
let values = yield m.getValues(); let values = yield m.getValues();
do_check_eq(values.days.size, 2); do_check_eq(values.days.size, 2);
do_check_true(values.days.hasDay(day1)); do_check_true(values.days.hasDay(day1));
@ -81,6 +93,10 @@ add_task(function* test_collect() {
let value = values.days.getDay(day1); let value = values.days.getDay(day1);
do_check_true(value.has("main-crash")); do_check_true(value.has("main-crash"));
do_check_eq(value.get("main-crash"), 2); 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_true(value.has("content-hang"));
do_check_eq(value.get("content-hang"), 1); do_check_eq(value.get("content-hang"), 1);
do_check_true(value.has("plugin-crash")); 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_eq(value.get("main-hang"), 1);
do_check_true(value.has("content-crash")); do_check_true(value.has("content-crash"));
do_check_eq(value.get("content-crash"), 1); 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_true(value.has("plugin-hang"));
do_check_eq(value.get("plugin-hang"), 1); 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. // A crash in a plugin process.
PROCESS_TYPE_PLUGIN: "plugin", PROCESS_TYPE_PLUGIN: "plugin",
// A submission of a crash.
PROCESS_TYPE_SUBMISSION: "submission",
// A real crash. // A real crash.
CRASH_TYPE_CRASH: "crash", CRASH_TYPE_CRASH: "crash",
// A hang. // A hang.
CRASH_TYPE_HANG: "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, 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, 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: /^(.*)$/, ALL_REGEX: /^(.*)$/,
@ -360,6 +369,38 @@ this.CrashManager.prototype = Object.freeze({
}.bind(this)); }.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. * 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. // 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 // Do not change the format of an existing type. Instead, invent a new
// type. // type.
// DO NOT ADD NEW TYPES WITHOUT DOCUMENTING!
let lines = payload.split("\n");
// type in event file => [processType, crashType] switch (type) {
let eventMap = { case "crash.main.1":
"crash.main.1": ["main", "crash"], 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) { case "crash.submission.1":
let lines = payload.split("\n"); if (lines.length == 3) {
if (lines.length > 1) { this._addSubmissionAsCrash(store, this.PROCESS_TYPE_MAIN,
this._log.warn("Multiple lines unexpected in payload for " + this.CRASH_TYPE_CRASH,
entry.path); lines[1] === "true", lines[0], date);
return this.EVENT_FILE_ERROR_MALFORMED; } else {
} return this.EVENT_FILE_ERROR_MALFORMED;
}
break;
store.addCrash(...eventMap[type], payload, date); default:
return this.EVENT_FILE_SUCCESS; return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
} }
// DO NOT ADD NEW TYPES WITHOUT DOCUMENTING! return this.EVENT_FILE_SUCCESS;
return this.EVENT_FILE_ERROR_UNKNOWN_EVENT;
}, },
/** /**

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 There should be ``UUID.dmp`` and ``UUID.extra`` files on disk, saved by
Breakpad. 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 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.equal(crash.type, m.PROCESS_TYPE_PLUGIN + "-" + m.CRASH_TYPE_HANG);
Assert.ok(crash.isOfType(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_MAIN,
PROCESS_TYPE_CONTENT, PROCESS_TYPE_CONTENT,
PROCESS_TYPE_PLUGIN, PROCESS_TYPE_PLUGIN,
PROCESS_TYPE_SUBMISSION,
CRASH_TYPE_CRASH, CRASH_TYPE_CRASH,
CRASH_TYPE_HANG, CRASH_TYPE_HANG,
SUBMISSION_TYPE_SUCCEEDED,
SUBMISSION_TYPE_FAILED,
} = CrashManager.prototype; } = CrashManager.prototype;
const CrashStore = bsp.CrashStore; const CrashStore = bsp.CrashStore;
@ -273,6 +276,44 @@ add_task(function* test_add_plugin_hang() {
Assert.equal(crashes.length, 2); 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() { add_task(function* test_add_mixed_types() {
let s = yield getStore(); let s = yield getStore();

View File

@ -32,9 +32,12 @@ namespace CrashReporter {
StringTable gStrings; StringTable gStrings;
string gSettingsPath; string gSettingsPath;
string gEventsPath;
int gArgc; int gArgc;
char** gArgv; char** gArgv;
enum SubmissionResult {Succeeded, Failed};
static auto_ptr<ofstream> gLogStream(nullptr); static auto_ptr<ofstream> gLogStream(nullptr);
static string gReporterDumpFile; static string gReporterDumpFile;
static string gExtraFile; static string gExtraFile;
@ -172,6 +175,53 @@ bool WriteStringsToFile(const string& path,
return success; 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) void LogMessage(const std::string& message)
{ {
if (gLogStream.get()) { if (gLogStream.get()) {
@ -218,15 +268,6 @@ static string GetExtraDataFilename(const string& dumpfile)
return filename; 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, static bool MoveCrashData(const string& toDir,
string& dumpfile, string& dumpfile,
string& extrafile) string& extrafile)
@ -316,6 +357,7 @@ static bool AddSubmittedReport(const string& serverResponse)
file->close(); file->close();
delete file; delete file;
WriteSubmissionEvent(Succeeded, responseItems["CrashID"]);
return true; return true;
} }
@ -343,7 +385,10 @@ void SendCompleted(bool success, const string& serverResponse)
return; return;
directory.resize(slashpos); directory.resize(slashpos);
UIPruneSavedDumps(directory); UIPruneSavedDumps(directory);
WriteSubmissionEvent(Failed, "");
} }
} else {
WriteSubmissionEvent(Failed, "");
} }
} }
@ -514,6 +559,23 @@ int main(int argc, char** argv)
OpenLogFile(); 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)) { if (!UIFileExists(gReporterDumpFile)) {
UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]); UIError(gStrings[ST_ERROR_DUMPFILEEXISTS]);
return 0; return 0;

View File

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

View File

@ -163,6 +163,7 @@ static XP_CHAR* crashReporterPath;
// Where crash events should go. // Where crash events should go.
static XP_CHAR* eventsDirectory; static XP_CHAR* eventsDirectory;
static char* eventsEnv = nullptr;
// If this is false, we don't launch the crash reporter // If this is false, we don't launch the crash reporter
static bool doReport = true; static bool doReport = true;
@ -2107,10 +2108,29 @@ SetCrashEventsDir(nsIFile* aDir)
nsString path; nsString path;
eventsDir->GetPath(path); eventsDir->GetPath(path);
eventsDirectory = reinterpret_cast<wchar_t*>(ToNewUnicode(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 #else
nsCString path; nsCString path;
eventsDir->GetNativePath(path); eventsDir->GetNativePath(path);
eventsDirectory = ToNewCString(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 #endif
} }

View File

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

View File

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

View File

@ -6,6 +6,7 @@
* the code you put here can be evaluated by both! */ * the code you put here can be evaluated by both! */
Cu.import("resource://webapprt/modules/WebappRT.jsm"); 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, // When WebappsHandler opens an install confirmation dialog for apps we install,
// close it, which will be seen as the equivalent of cancelling the 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. * The callback to call once the transmogrification is complete.
*/ */
function becomeWebapp(manifestURL, parameters, onBecome) { 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"); Services.obs.removeObserver(observeInstall, "webapps-ask-install");
// Step 2: Configure the runtime session to represent the app. // Step 2: Configure the runtime session to represent the app.
@ -52,7 +53,7 @@ function becomeWebapp(manifestURL, parameters, onBecome) {
let scope = {}; let scope = {};
Cu.import("resource://gre/modules/Webapps.jsm", scope); Cu.import("resource://gre/modules/Webapps.jsm", scope);
Cu.import("resource://webapprt/modules/Startup.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); let installRecord = JSON.parse(data);
installRecord.mm = subj; installRecord.mm = subj;
@ -68,20 +69,20 @@ function becomeWebapp(manifestURL, parameters, onBecome) {
null); null);
} }
let promise = scope.startup(win);
// During chrome tests, we use the same window to load all the tests. We // 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 // need to change the buildID so that the permissions for the currently
// tested application get installed. // tested application get installed.
Services.prefs.setCharPref("webapprt.buildID", WebappRT.config.app.manifestURL); Services.prefs.setCharPref("webapprt.buildID", WebappRT.config.app.manifestURL);
// During tests, the webapps registry is already loaded. // During tests, the webapps registry is already loaded,
// The Startup module needs to be notified when the webapps registry // but SystemMessageInternal expects to be notified when the registry
// gets loaded, so we do that now. // 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-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); Services.obs.addObserver(observeInstall, "webapps-ask-install", false);
// Step 1: Install the app at the URL specified by the manifest. // 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"?> <?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"> xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/javascript" src="chrome://webapprt/content/mochitest.js"/> <script type="application/javascript" src="chrome://webapprt/content/mochitest.js"/>