Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2013-10-24 07:08:01 +02:00
commit d2da7791d3
124 changed files with 2660 additions and 967 deletions

View File

@ -18,4 +18,4 @@
# Modifying this file will now automatically clobber the buildbot machines \o/
#
Bug 918207 needed a clobber on every platform because we can't have nice things
Bug 899574 needed a clobber on every platform because we can't have nice things

View File

@ -11,6 +11,7 @@ Cu.import('resource://gre/modules/AlarmService.jsm');
Cu.import('resource://gre/modules/ActivitiesService.jsm');
Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
Cu.import('resource://gre/modules/ObjectWrapper.jsm');
Cu.import('resource://gre/modules/NotificationDB.jsm');
Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
Cu.import('resource://gre/modules/Payment.jsm');
Cu.import("resource://gre/modules/AppsUtils.jsm");

View File

@ -1,4 +1,4 @@
{
"revision": "a57a913f1dd723afa191124f27b8d9fc4b0cb1c0",
"revision": "20e3f42ccb6073c6d9bc9741de3a19a939a8a7d8",
"repo_path": "/integration/gaia-central"
}

View File

@ -352,6 +352,8 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/PhoneNumberService.js
@BINPATH@/components/PhoneNumberService.manifest
@BINPATH@/components/NotificationStorage.js
@BINPATH@/components/NotificationStorage.manifest
@BINPATH@/components/PermissionSettings.js
@BINPATH@/components/PermissionSettings.manifest
@BINPATH@/components/PermissionPromptService.js
@ -543,9 +545,6 @@
@BINPATH@/components/TCPSocketParentIntermediary.js
@BINPATH@/components/TCPSocket.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest
@BINPATH@/components/Payment.js
@BINPATH@/components/PaymentFlowInfo.js
@BINPATH@/components/PaymentRequestInfo.js

View File

@ -365,12 +365,6 @@ pref("browser.download.manager.quitBehavior", 0);
pref("browser.download.manager.scanWhenDone", true);
pref("browser.download.manager.resumeOnWakeDelay", 10000);
// Enables the asynchronous Downloads API in the Downloads Panel.
pref("browser.download.useJSTransfer", true);
// This allows disabling the Downloads Panel in favor of the old interface.
pref("browser.download.useToolkitUI", false);
// This allows disabling the animated notifications shown by
// the Downloads Indicator when a download starts or completes.
pref("browser.download.animateNotifications", true);

View File

@ -7,6 +7,7 @@ let Ci = Components.interfaces;
let Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",

View File

@ -235,15 +235,12 @@ this.DownloadsCommon = {
},
/**
* Indicates whether we should show the full Download Manager window interface
* instead of the simplified panel interface. The behavior of downloads
* across browsing session is consistent with the selected interface.
* Indicates that we should show the simplified panel interface instead of the
* full Download Manager window interface. The code associated with the
* Download Manager window interface will be removed in bug 899110.
*/
get useToolkitUI()
{
try {
return Services.prefs.getBoolPref("browser.download.useToolkitUI");
} catch (ex) { }
return false;
},
@ -570,17 +567,12 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
});
/**
* Returns true if we should hook the panel to the JavaScript API for downloads
* instead of the nsIDownloadManager back-end. In order for the logic to work
* properly, this value never changes during the execution of the application,
* even if the underlying preference value has changed. A restart is required
* for the change to take effect.
* Returns true to indicate that we should hook the panel to the JavaScript API
* for downloads instead of the nsIDownloadManager back-end. The code
* associated with nsIDownloadManager will be removed in bug 899110.
*/
XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () {
try {
return Services.prefs.getBoolPref("browser.download.useJSTransfer");
} catch (ex) { }
return false;
return true;
});
////////////////////////////////////////////////////////////////////////////////

View File

@ -93,29 +93,13 @@ DownloadsStartup.prototype = {
.registerFactory(kDownloadsUICid, "",
kDownloadsUIContractId, null);
// If the integration preference is enabled, override Toolkit's
// nsITransfer implementation with the one from the JavaScript API for
// downloads. This should be used only by developers while testing new
// code that uses the JavaScript API, and will eventually be removed
// when nsIDownloadManager will not be available anymore (bug 851471).
let useJSTransfer = false;
try {
// For performance reasons, we don't want to load the DownloadsCommon
// module during startup, so we read the preference value directly.
useJSTransfer =
Services.prefs.getBoolPref("browser.download.useJSTransfer");
} catch (ex) { }
if (useJSTransfer) {
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kTransferCid, "",
kTransferContractId, null);
} else {
// The other notifications are handled internally by the JavaScript
// API for downloads, no need to observe when that API is enabled.
for (let topic of kObservedTopics) {
Services.obs.addObserver(this, topic, true);
}
}
// Override Toolkit's nsITransfer implementation with the one from the
// JavaScript API for downloads. This will eventually be removed when
// nsIDownloadManager will not be available anymore (bug 851471). The
// old code in this module will be removed in bug 899110.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kTransferCid, "",
kTransferContractId, null);
break;
case "sessionstore-windows-restored":

View File

@ -1420,9 +1420,6 @@ BrowserGlue.prototype = {
}
this._setPersist(toolbarResource, currentsetResource, currentset);
}
Services.prefs.clearUserPref("browser.download.useToolkitUI");
Services.prefs.clearUserPref("browser.library.useNewDownloadsView");
}
#ifdef XP_WIN

View File

@ -350,6 +350,10 @@ let SessionStoreInternal = {
// See bug 516755.
_disabledForMultiProcess: false,
// Promise that is resolved when we're ready to initialize
// and restore the session.
_promiseReadyForInitialization: null,
/**
* A promise fulfilled once initialization is complete.
*/
@ -870,13 +874,33 @@ let SessionStoreInternal = {
return;
}
// The very first window that is opened creates a promise that is then
// re-used by all subsequent windows. The promise will be used to tell
// when we're ready for initialization.
if (!this._promiseReadyForInitialization) {
let deferred = Promise.defer();
// Wait for the given window's delayed startup to be finished.
Services.obs.addObserver(function obs(subject, topic) {
if (aWindow == subject) {
Services.obs.removeObserver(obs, topic);
deferred.resolve();
}
}, "browser-delayed-startup-finished", false);
// We are ready for initialization as soon as the session file has been
// read from disk and the initial window's delayed startup has finished.
this._promiseReadyForInitialization =
Promise.all([deferred.promise, gSessionStartup.onceInitialized]);
}
// We can't call this.onLoad since initialization
// hasn't completed, so we'll wait until it is done.
// Even if additional windows are opened and wait
// for initialization as well, the first opened
// window should execute first, and this.onLoad
// will be called with the initialState.
gSessionStartup.onceInitialized.then(() => {
this._promiseReadyForInitialization.then(() => {
if (aWindow.closed) {
return;
}

View File

@ -61,3 +61,5 @@ fi
MOZ_WEBGL_CONFORMANT=1
# Enable navigator.mozPay
MOZ_PAY=1
MOZ_JSDOWNLOADS=1

View File

@ -1156,8 +1156,13 @@ SourceScripts.prototype = {
_onBlackBoxChange: function (aEvent, { url, isBlackBoxed }) {
const item = DebuggerView.Sources.getItemByValue(url);
if (item) {
DebuggerView.Sources.callMethod("checkItem", item.target, !isBlackBoxed);
if (isBlackBoxed) {
item.target.classList.add("black-boxed");
} else {
item.target.classList.remove("black-boxed");
}
}
DebuggerView.Sources.updateToolbarButtonsState();
DebuggerView.maybeShowBlackBoxMessage();
},

View File

@ -19,7 +19,7 @@ function SourcesView() {
this._onSourceSelect = this._onSourceSelect.bind(this);
this._onSourceClick = this._onSourceClick.bind(this);
this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
this._onSourceCheck = this._onSourceCheck.bind(this);
this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
this._onBreakpointClick = this._onBreakpointClick.bind(this);
this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
@ -28,7 +28,7 @@ function SourcesView() {
this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
this._onConditionalTextboxInput = this._onConditionalTextboxInput.bind(this);
this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
this._updatePrettyPrintButtonState = this._updatePrettyPrintButtonState.bind(this);
this.updateToolbarButtonsState = this.updateToolbarButtonsState.bind(this);
}
SourcesView.prototype = Heritage.extend(WidgetMethods, {
@ -39,7 +39,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
dumpn("Initializing the SourcesView");
this.widget = new SideMenuWidget(document.getElementById("sources"), {
showItemCheckboxes: true,
showArrows: true
});
@ -51,6 +50,7 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
this._cmPopup = document.getElementById("sourceEditorContextMenu");
this._cbPanel = document.getElementById("conditional-breakpoint-panel");
this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
this._blackBoxButton = document.getElementById("black-box");
this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
this._prettyPrintButton = document.getElementById("pretty-print");
@ -62,7 +62,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
this.widget.addEventListener("select", this._onSourceSelect, false);
this.widget.addEventListener("click", this._onSourceClick, false);
this.widget.addEventListener("check", this._onSourceCheck, false);
this._stopBlackBoxButton.addEventListener("click", this._onStopBlackBoxing, false);
this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
@ -86,7 +85,6 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
window.off(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
this.widget.removeEventListener("select", this._onSourceSelect, false);
this.widget.removeEventListener("click", this._onSourceClick, false);
this.widget.removeEventListener("check", this._onSourceCheck, false);
this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
@ -702,22 +700,23 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
DebuggerView.maybeShowBlackBoxMessage();
this._updatePrettyPrintButtonState();
this.updateToolbarButtonsState();
},
/**
* Enable or disable the pretty print button depending on whether the selected
* source is black boxed or not and check or uncheck it depending on if the
* selected source is already pretty printed or not.
* Update the checked/unchecked and enabled/disabled states of the buttons in
* the sources toolbar based on the currently selected source's state.
*/
_updatePrettyPrintButtonState: function() {
updateToolbarButtonsState: function() {
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
if (sourceClient.isBlackBoxed) {
this._prettyPrintButton.setAttribute("disabled", true);
this._blackBoxButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
this._blackBoxButton.removeAttribute("checked");
}
if (sourceClient.isPrettyPrinted) {
@ -736,26 +735,30 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
},
/**
* The check listener for the sources container.
* Toggle the black boxed state of the selected source.
*/
_onSourceCheck: function({ detail: { checked }, target }) {
const shouldBlackBox = !checked;
toggleBlackBoxing: function() {
const { source } = this.selectedItem.attachment;
const sourceClient = gThreadClient.source(source);
const shouldBlackBox = !sourceClient.isBlackBoxed;
// Be optimistic that the (un-)black boxing will succeed and enable/disable
// the pretty print button immediately. Then, once we actually get the
// results from the server, make sure that it is in the correct state again
// by calling `_updatePrettyPrintButtonState`.
// Be optimistic that the (un-)black boxing will succeed, so enable/disable
// the pretty print button and check/uncheck the black box button
// immediately. Then, once we actually get the results from the server, make
// sure that it is in the correct state again by calling
// `updateToolbarButtonsState`.
if (shouldBlackBox) {
this._prettyPrintButton.setAttribute("disabled", true);
this._blackBoxButton.setAttribute("checked", true);
} else {
this._prettyPrintButton.removeAttribute("disabled");
this._blackBoxButton.removeAttribute("checked");
}
const { source } = this.getItemForElement(target).attachment;
DebuggerController.SourceScripts.blackBox(source, shouldBlackBox)
.then(this._updatePrettyPrintButtonState,
this._updatePrettyPrintButtonState);
.then(this.updateToolbarButtonsState,
this.updateToolbarButtonsState);
},
/**
@ -763,7 +766,9 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
*/
_onStopBlackBoxing: function() {
let sourceForm = this.selectedItem.attachment.source;
DebuggerController.SourceScripts.blackBox(sourceForm, false);
DebuggerController.SourceScripts.blackBox(sourceForm, false)
.then(this.updateToolbarButtonsState,
this.updateToolbarButtonsState);
},
/**

View File

@ -32,6 +32,8 @@
<commandset id="debuggerCommands">
<command id="prettyPrintCommand"
oncommand="DebuggerView.Sources.togglePrettyPrint()"/>
<command id="blackBoxCommand"
oncommand="DebuggerView.Sources.toggleBlackBoxing()"/>
<command id="unBlackBoxButton"
oncommand="DebuggerView.Sources._onStopBlackBoxing()"/>
<command id="nextSourceCommand"
@ -320,12 +322,18 @@
<vbox id="sources-pane">
<vbox id="sources" flex="1"/>
<toolbar id="sources-toolbar" class="devtools-toolbar">
<toolbarbutton id="pretty-print"
label="{}"
tooltiptext="&debuggerUI.sources.prettyPrint;"
class="devtools-toolbarbutton devtools-monospace"
command="prettyPrintCommand"
hidden="true"/>
<hbox id="sources-controls">
<toolbarbutton id="black-box"
tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
command="blackBoxCommand"
class="devtools-toolbarbutton"/>
<toolbarbutton id="pretty-print"
label="{}"
tooltiptext="&debuggerUI.sources.prettyPrint;"
class="devtools-toolbarbutton devtools-monospace"
command="prettyPrintCommand"
hidden="true"/>
</hbox>
</toolbar>
</vbox>
<splitter id="sources-and-editor-splitter"

View File

@ -65,7 +65,6 @@ support-files =
[browser_dbg_blackboxing-04.js]
[browser_dbg_blackboxing-05.js]
[browser_dbg_blackboxing-06.js]
[browser_dbg_blackboxing-07.js]
[browser_dbg_file-reload.js]
[browser_dbg_breadcrumbs-access.js]
[browser_dbg_break-on-dom-01.js]

View File

@ -27,24 +27,19 @@ function test() {
}
function testBlackBoxSource() {
const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
ok(checkbox, "Should get the checkbox for black boxing the source.");
ok(checkbox.checked, "Should not be black boxed by default.");
const bbButton = getBlackBoxButton(gPanel);
ok(!bbButton.checked, "Should not be black boxed by default");
let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
return toggleBlackBoxing(gPanel).then(aSource => {
ok(aSource.isBlackBoxed, "The source should be black boxed now.");
ok(!checkbox.checked, "The checkbox should no longer be checked.");
ok(bbButton.checked, "The checkbox should no longer be checked.");
});
checkbox.click();
return finished;
}
function testBlackBoxReload() {
return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
ok(checkbox, "Should get the checkbox for black boxing the source.");
ok(!checkbox.checked, "Should still be black boxed.");
const bbButton = getBlackBoxButton(gPanel);
ok(bbButton.checked, "Should still be black boxed.");
});
}

View File

@ -31,12 +31,9 @@ function test() {
}
function testBlackBoxSource() {
let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
return toggleBlackBoxing(gPanel).then(aSource => {
ok(aSource.isBlackBoxed, "The source should be black boxed now.");
});
getBlackBoxCheckbox(BLACKBOXME_URL).click();
return finished;
}
function testBlackBoxStack() {
@ -53,12 +50,6 @@ function testBlackBoxStack() {
return finished;
}
function getBlackBoxCheckbox(aUrl) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
".side-menu-widget-item-checkbox");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;

View File

@ -40,7 +40,7 @@ function testBlackBoxStack() {
}
function testBlackBoxSource() {
let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
return toggleBlackBoxing(gPanel, BLACKBOXME_URL).then(aSource => {
ok(aSource.isBlackBoxed, "The source should be black boxed now.");
is(gFrames.itemCount, 3,
@ -48,15 +48,6 @@ function testBlackBoxSource() {
is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
"And one of them should be the combined black boxed frames.");
});
getBlackBoxCheckbox(BLACKBOXME_URL).click();
return finished;
}
function getBlackBoxCheckbox(aUrl) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
".side-menu-widget-item-checkbox");
}
registerCleanupFunction(function() {

View File

@ -32,9 +32,9 @@ function test() {
function blackBoxSources() {
let finished = waitForThreadEvents(gPanel, "blackboxchange", 3);
getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_one.js").click();
getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_two.js").click();
getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_three.js").click();
toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_one.js");
toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_two.js");
toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_three.js");
return finished;
}
@ -52,12 +52,6 @@ function testBlackBoxStack() {
return finished;
}
function getBlackBoxCheckbox(aUrl) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
".side-menu-widget-item-checkbox");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;

View File

@ -21,7 +21,7 @@ function test() {
waitForSourceShown(gPanel, ".coffee")
.then(testSourceEditorShown)
.then(blackBoxSource)
.then(toggleBlackBoxing.bind(null, gPanel))
.then(testBlackBoxMessageShown)
.then(clickStopBlackBoxingButton)
.then(testSourceEditorShownAgain)
@ -37,12 +37,6 @@ function testSourceEditorShown() {
"The first item in the deck should be selected (the source editor).");
}
function blackBoxSource() {
let finished = waitForThreadEvents(gPanel, "blackboxchange");
getAnyBlackBoxCheckbox().click();
return finished;
}
function testBlackBoxMessageShown() {
is(gDeck.selectedIndex, "1",
"The second item in the deck should be selected (the black box message).");
@ -59,11 +53,6 @@ function testSourceEditorShownAgain() {
"The first item in the deck should be selected again (the source editor).");
}
function getAnyBlackBoxCheckbox() {
return gDebugger.document.querySelector(
".side-menu-widget-item .side-menu-widget-item-checkbox");
}
function getEditorBlackboxMessageButton() {
return gDebugger.document.getElementById("black-boxed-message-button");
}

View File

@ -2,7 +2,8 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that clicking the black box checkbox doesn't select that source.
* Test that clicking the black box checkbox when paused doesn't re-select the
* currently paused frame's source.
*/
const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
@ -18,35 +19,35 @@ function test() {
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
waitForSourceShown(gPanel, ".js")
waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
.then(testBlackBox)
.then(() => closeDebuggerAndFinish(gPanel))
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.runTest();
});
}
function testBlackBox() {
const selectedUrl = gSources.selectedValue;
const checkbox = getDifferentBlackBoxCheckbox(selectedUrl);
ok(checkbox, "We should be able to grab a different checkbox.");
let finished = waitForThreadEvents(gPanel, "blackboxchange").then(() => {
is(selectedUrl, gSources.selectedValue,
"The same source should still be selected.");
let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
const newSelectedUrl = gSources.selectedValue;
isnot(selectedUrl, newSelectedUrl,
"Should not have the same url selected.");
return toggleBlackBoxing(gPanel).then(() => {
is(gSources.selectedValue, newSelectedUrl,
"The selected source did not change.");
});
});
checkbox.click();
gSources.selectedIndex = 0;
return finished;
}
function getDifferentBlackBoxCheckbox(aUrl) {
return gDebugger.document.querySelector(
".side-menu-widget-item:not([tooltiptext=\"" + aUrl + "\"]) " +
".side-menu-widget-item-checkbox");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;

View File

@ -1,66 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Test that clicking the black box checkbox when paused doesn't re-select the
* currently paused frame's source.
*/
const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
let gTab, gDebuggee, gPanel, gDebugger;
let gSources;
function test() {
initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
gTab = aTab;
gDebuggee = aDebuggee;
gPanel = aPanel;
gDebugger = gPanel.panelWin;
gSources = gDebugger.DebuggerView.Sources;
waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
.then(testBlackBox)
.then(() => resumeDebuggerThenCloseAndFinish(gPanel))
.then(null, aError => {
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
});
gDebuggee.runTest();
});
}
function testBlackBox() {
const selectedUrl = gSources.selectedValue;
let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
const newSelectedUrl = gSources.selectedValue;
isnot(selectedUrl, newSelectedUrl,
"Should not have the same url selected.");
let finished = waitForThreadEvents(gPanel, "blackboxchange").then(() => {
is(gSources.selectedValue, newSelectedUrl,
"The selected source did not change.");
});
getBlackBoxCheckbox(newSelectedUrl).click()
return finished;
});
gSources.selectedIndex = 0;
return finished;
}
function getBlackBoxCheckbox(aUrl) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
".side-menu-widget-item-checkbox");
}
registerCleanupFunction(function() {
gTab = null;
gDebuggee = null;
gPanel = null;
gDebugger = null;
gSources = null;
});

View File

@ -46,69 +46,95 @@ function waitForDebuggerSources() {
}
function testBlackBoxSource() {
return cmd("dbg blackbox " + BLACKBOXME_URL).then(() => {
const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
ok(!checkbox.checked,
"Should be able to black box a specific source.");
return Task.spawn(function* () {
yield cmd("dbg blackbox " + BLACKBOXME_URL);
let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
ok(bbButton.checked,
"Should be able to black box a specific source.");
});
}
function testUnBlackBoxSource() {
return cmd("dbg unblackbox " + BLACKBOXME_URL).then(() => {
const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
ok(checkbox.checked,
"Should be able to stop black boxing a specific source.");
return Task.spawn(function* () {
yield cmd("dbg unblackbox " + BLACKBOXME_URL);
let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
ok(!bbButton.checked,
"Should be able to stop black boxing a specific source.");
});
}
function testBlackBoxGlob() {
return cmd("dbg blackbox --glob *blackboxing_t*.js", 2,
[/blackboxing_three\.js/g, /blackboxing_two\.js/g]).then(() => {
ok(getBlackBoxCheckbox(BLACKBOXME_URL).checked,
"blackboxme should not be black boxed because it doesn't match the glob.");
ok(getBlackBoxCheckbox(BLACKBOXONE_URL).checked,
"blackbox_one should not be black boxed because it doesn't match the glob.");
return Task.spawn(function* () {
yield cmd("dbg blackbox --glob *blackboxing_t*.js", 2,
[/blackboxing_three\.js/g, /blackboxing_two\.js/g]);
ok(!getBlackBoxCheckbox(BLACKBOXTWO_URL).checked,
"blackbox_two should be black boxed because it matches the glob.");
ok(!getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked,
let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
ok(!bbButton.checked,
"blackboxme should not be black boxed because it doesn't match the glob.");
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXONE_URL);
ok(!bbButton.checked,
"blackbox_one should not be black boxed because it doesn't match the glob.");
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTWO_URL);
ok(bbButton.checked,
"blackbox_two should be black boxed because it matches the glob.");
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTHREE_URL);
ok(bbButton.checked,
"blackbox_three should be black boxed because it matches the glob.");
});
}
function testUnBlackBoxGlob() {
return cmd("dbg unblackbox --glob *blackboxing_t*.js", 2).then(() => {
ok(getBlackBoxCheckbox(BLACKBOXTWO_URL).checked,
"blackbox_two should be un-black boxed because it matches the glob.");
ok(getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked,
return Task.spawn(function* () {
yield cmd("dbg unblackbox --glob *blackboxing_t*.js", 2);
let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTWO_URL);
ok(!bbButton.checked,
"blackbox_two should be un-black boxed because it matches the glob.");
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTHREE_URL);
ok(!bbButton.checked,
"blackbox_three should be un-black boxed because it matches the glob.");
});
}
function testBlackBoxInvert() {
return cmd("dbg blackbox --invert --glob *blackboxing_t*.js", 3,
[/blackboxing_three\.js/g, /blackboxing_two\.js/g]).then(() => {
ok(!getBlackBoxCheckbox(BLACKBOXME_URL).checked,
return Task.spawn(function* () {
yield cmd("dbg blackbox --invert --glob *blackboxing_t*.js", 3,
[/blackboxing_three\.js/g, /blackboxing_two\.js/g]);
let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
ok(bbButton.checked,
"blackboxme should be black boxed because it doesn't match the glob.");
ok(!getBlackBoxCheckbox(BLACKBOXONE_URL).checked,
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXONE_URL);
ok(bbButton.checked,
"blackbox_one should be black boxed because it doesn't match the glob.");
ok(!getBlackBoxCheckbox(TEST_URL).checked,
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, TEST_URL);
ok(bbButton.checked,
"TEST_URL should be black boxed because it doesn't match the glob.");
ok(getBlackBoxCheckbox(BLACKBOXTWO_URL).checked,
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTWO_URL);
ok(!bbButton.checked,
"blackbox_two should not be black boxed because it matches the glob.");
ok(getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked,
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTHREE_URL);
ok(!bbButton.checked,
"blackbox_three should not be black boxed because it matches the glob.");
});
}
function testUnBlackBoxInvert() {
return cmd("dbg unblackbox --invert --glob *blackboxing_t*.js", 3).then(() => {
ok(getBlackBoxCheckbox(BLACKBOXME_URL).checked,
return Task.spawn(function* () {
yield cmd("dbg unblackbox --invert --glob *blackboxing_t*.js", 3);
let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
ok(!bbButton.checked,
"blackboxme should be un-black boxed because it does not match the glob.");
ok(getBlackBoxCheckbox(BLACKBOXONE_URL).checked,
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXONE_URL);
ok(!bbButton.checked,
"blackbox_one should be un-black boxed because it does not match the glob.");
ok(getBlackBoxCheckbox(TEST_URL).checked,
bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, TEST_URL);
ok(!bbButton.checked,
"TEST_URL should be un-black boxed because it doesn't match the glob.");
});
}
@ -126,9 +152,3 @@ function cmd(aTyped, aEventRepeat = 1, aOutput = "") {
helpers.audit(gOptions, [{ setup: aTyped, output: aOutput, exec: {} }])
]);
}
function getBlackBoxCheckbox(url) {
return gDebugger.document.querySelector(
".side-menu-widget-item[tooltiptext=\"" + url + "\"] " +
".side-menu-widget-item-checkbox");
}

View File

@ -22,8 +22,7 @@ function test() {
waitForSourceShown(gPanel, "code_ugly.js")
.then(testSourceIsUgly)
.then(blackBoxSource)
.then(waitForThreadEvents.bind(null, gPanel, "blackboxchange"))
.then(toggleBlackBoxing.bind(null, gPanel))
.then(clickPrettyPrintButton)
.then(testSourceIsStillUgly)
.then(() => closeDebuggerAndFinish(gPanel))
@ -38,12 +37,6 @@ function testSourceIsUgly() {
"The source shouldn't be pretty printed yet.");
}
function blackBoxSource() {
const checkbox = gDebugger.document.querySelector(
".selected .side-menu-widget-item-checkbox");
checkbox.click();
}
function clickPrettyPrintButton() {
gDebugger.document.getElementById("pretty-print").click();
}

View File

@ -523,3 +523,29 @@ function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) {
thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve));
return deferred.promise;
}
function getBlackBoxButton(aPanel) {
return aPanel.panelWin.document.getElementById("black-box");
}
function toggleBlackBoxing(aPanel, aSource = null) {
function clickBlackBoxButton() {
getBlackBoxButton(aPanel).click();
}
const blackBoxChanged = waitForThreadEvents(aPanel, "blackboxchange");
if (aSource) {
aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
ensureSourceIs(aPanel, aSource, true).then(clickBlackBoxButton);
} else {
clickBlackBoxButton();
}
return blackBoxChanged;
}
function selectSourceAndGetBlackBoxButton(aPanel, aSource) {
aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
return ensureSourceIs(aPanel, aSource, true)
.then(getBlackBoxButton.bind(null, aPanel));
}

View File

@ -54,7 +54,14 @@ function submit() {
Services.prefs.setIntPref("devtools.debugger.remote-port", port);
// Initiate the connection
let transport = debuggerSocketConnect(host, port);
let transport;
try {
transport = debuggerSocketConnect(host, port);
} catch(e) {
// Bug 921850: catch rare exception from debuggerSocketConnect
showError("unexpected");
return;
}
gClient = new DebuggerClient(transport);
let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
gConnectionTimeout = setTimeout(handleConnectionTimeout, delay);

View File

@ -18,8 +18,9 @@ To confirm the functionality run mochitests for the following components:
The sourceeditor component contains imported CodeMirror tests [3]. Some
tests were commented out because we don't use that functionality within
Firefox (for example Ruby editing mode). Other than that, we don't have
any Mozilla-specific patches applied to CodeMirror itself.
Firefox (for example Ruby editing mode). The search addon (search.js)
was slightly modified to make search UI localizable. Other than that,
we don't have any Mozilla-specific patches applied to CodeMirror itself.
# Addons

View File

@ -45,12 +45,16 @@
var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
}
var queryDialog =
'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
var queryDialog;
function doSearch(cm, rev) {
if (!queryDialog) {
queryDialog = cm.l10n('findCmd.promptMessage') +
' <input type="text" style="width: 10em"/>';
}
var state = getSearchState(cm);
if (state.query) return findNext(cm, rev);
dialog(cm, queryDialog, "Search for:", function(query) {
dialog(cm, queryDialog, cm.l10n('findCmd.promptMessage'), function(query) {
cm.operation(function() {
if (!query || state.query) return;
state.query = parseQuery(query);

View File

@ -207,6 +207,10 @@ Editor.prototype = {
cm.on("gutterClick", (cm, line) => this.emit("gutterClick", line));
cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
win.CodeMirror.defineExtension("l10n", (name) => {
return L10N.GetStringFromName(name);
});
doc.defaultView.controllers.insertControllerAt(0, controller(this, doc.defaultView));
this.container = env;

View File

@ -94,6 +94,9 @@ support-files =
test_bug_770099_violation.html
test_bug_770099_violation.html^headers^
testscript.js
test-bug_923281_console_log_filter.html
test-bug_923281_test1.js
test-bug_923281_test2.js
[browser_bug664688_sandbox_update_after_navigation.js]
[browser_bug_638949_copy_link_location.js]
@ -231,3 +234,4 @@ support-files =
[browser_webconsole_property_provider.js]
[browser_webconsole_scratchpad_panel_link.js]
[browser_webconsole_view_source.js]
[browser_webconsole_log_file_filter.js]

View File

@ -0,0 +1,80 @@
/* 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 text filter box works to filter based on filenames
// where the logs were generated.
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html";
let hud;
function test() {
addTab(TEST_URI);
browser.addEventListener("load", function onLoad() {
browser.removeEventListener("load", onLoad, true);
openConsole(null, consoleOpened);
}, true);
}
function consoleOpened(aHud) {
hud = aHud;
let console = content.console;
console.log("sentinel log");
waitForMessages({
webconsole: hud,
messages: [{
text: "sentinel log",
category: CATEGORY_WEBDEV,
severity: SEVERITY_LOG
}],
}).then(testLiveFilteringOnSearchStrings);
}
function testLiveFilteringOnSearchStrings() {
is(hud.outputNode.children.length, 4, "number of messages");
setStringFilter("random");
is(countMessageNodes(), 1, "the log nodes not containing string " +
"\"random\" are hidden");
setStringFilter("test2.js");
is(countMessageNodes(), 2, "show only log nodes containing string " +
"\"test2.js\" or log nodes created from files with filename " +
"containing \"test2.js\" as substring.");
setStringFilter("test1");
is(countMessageNodes(), 2, "show only log nodes containing string " +
"\"test1\" or log nodes created from files with filename " +
"containing \"test1\" as substring.");
setStringFilter("");
is(countMessageNodes(), 4, "show all log nodes on setting filter string " +
"as \"\".");
finishTest();
}
function countMessageNodes() {
let outputNode = hud.outputNode;
let messageNodes = outputNode.querySelectorAll(".message");
content.console.log(messageNodes.length);
let displayedMessageNodes = 0;
let view = hud.iframeWindow;
for (let i = 0; i < messageNodes.length; i++) {
let computedStyle = view.getComputedStyle(messageNodes[i], null);
if (computedStyle.display !== "none") {
displayedMessageNodes++;
}
}
return displayedMessageNodes;
}
function setStringFilter(aValue)
{
hud.ui.filterBox.value = aValue;
hud.ui.adjustVisibilityOnSearchStringChange();
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE HTML>
<html dir="ltr" xml:lang="en-US" lang="en-US">
<head>
<meta charset="utf-8">
<title>Console test</title>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<script type="text/javascript" src="test-bug_923281_test1.js"></script>
<script type="text/javascript" src="test-bug_923281_test2.js"></script>
</head>
<body></body>
</html>

View File

@ -0,0 +1,5 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
console.log("Sample log.");
console.log("This log should be filtered when filtered for test2.js.");

View File

@ -0,0 +1,4 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
console.log("This is a random text.");

View File

@ -873,7 +873,7 @@ WebConsoleFrame.prototype = {
let node = nodes[i];
// hide nodes that match the strings
let text = node.clipboardText;
let text = node.textContent;
// if the text matches the words in aSearchString...
if (this.stringMatchesFilters(text, searchString)) {

View File

@ -509,6 +509,8 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/PhoneNumberService.js
@BINPATH@/components/PhoneNumberService.manifest
@BINPATH@/components/NotificationStorage.js
@BINPATH@/components/NotificationStorage.manifest
@BINPATH@/components/AlarmsManager.js
@BINPATH@/components/AlarmsManager.manifest
@BINPATH@/components/Push.js
@ -519,9 +521,6 @@
@BINPATH@/components/TCPSocketParentIntermediary.js
@BINPATH@/components/TCPSocket.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest
@BINPATH@/components/Payment.js
@BINPATH@/components/PaymentFlowInfo.js
@BINPATH@/components/PaymentRequestInfo.js

View File

@ -33,8 +33,12 @@
- the button that opens up an options context menu for the debugger UI. -->
<!ENTITY debuggerUI.optsButton.tooltip "Debugger Options">
<!-- LOCALIZATION NOTE (debuggerUI.sources.blackBoxTooltip): This is the tooltip
- for the button that black boxes the selected source. -->
<!ENTITY debuggerUI.sources.blackBoxTooltip "Toggle Black Boxing">
<!-- LOCALIZATION NOTE (debuggerUI.sources.prettyPrint): This is the tooltip for the
button that pretty prints the selected source. -->
- button that pretty prints the selected source. -->
<!ENTITY debuggerUI.sources.prettyPrint "Prettify Source">
<!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the

View File

@ -36,15 +36,17 @@ pref("prompts.tab_modal.enabled", true);
pref("layers.offmainthreadcomposition.enabled", true);
pref("layers.async-pan-zoom.enabled", true);
pref("layers.componentalpha.enabled", false);
pref("gfx.azpc.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
pref("gfx.azpc.pan_repaint_interval", "50"); // prefer 20 fps
pref("gfx.azpc.fling_repaint_interval", "50"); // prefer 20 fps
pref("gfx.axis.fling_friction", "0.002");
pref("gfx.axis.fling_stopped_threshold", "0.2");
// Prefs to control the async pan/zoom behaviour
pref("apz.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
pref("apz.pan_repaint_interval", "50"); // prefer 20 fps
pref("apz.fling_repaint_interval", "50"); // prefer 20 fps
pref("apz.fling_friction", "0.002");
pref("apz.fling_stopped_threshold", "0.2");
// 0 = free, 1 = standard, 2 = sticky
pref("apzc.axis_lock_mode", 2);
pref("apzc.cross_slide.enabled", true);
pref("apz.axis_lock_mode", 2);
pref("apz.cross_slide.enabled", true);
// Enable Microsoft TSF support by default for imes.
pref("intl.enable_tsf_support", true);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

View File

@ -22,56 +22,29 @@
font-weight: bold;
}
#sources .side-menu-widget-item-checkbox {
-moz-appearance: none;
opacity: 0;
transition: opacity .15s ease 0s;
#black-box {
list-style-image: url(debugger-blackbox.png);
-moz-image-region: rect(0px,16px,16px,0px);
}
/* Only show the checkbox when the source is hovered over, is selected, or if it
* is not checked. */
#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item-checkbox:not([checked]) {
opacity: 1;
transition: opacity .15s ease 0s;
#black-box[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
#sources .side-menu-widget-item-checkbox > .checkbox-spacer-box {
-moz-appearance: none;
}
#sources .side-menu-widget-item-checkbox > .checkbox-spacer-box > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-size: 32px 16px;
background-position: -16px 0;
width: 16px;
height: 16px;
border: 0;
}
#sources .side-menu-widget-item-checkbox[checked] > .checkbox-spacer-box > .checkbox-check {
background-position: 0 0;
}
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
#sources .black-boxed {
color: #888;
}
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
#sources .black-boxed > .dbg-breakpoint {
display: none;
}
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
background-image: none;
box-shadow: inset -1px 0 0 #222426;
}
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
background-image: none;
box-shadow: inset 1px 0 0 #222426;
}
@ -147,7 +120,7 @@
/* Sources and breakpoints view */
.dbg-breakpoint {
-moz-margin-start: -14px;
-moz-margin-start: 4px;
}
.dbg-breakpoint-line {
@ -402,7 +375,8 @@
list-style-image: url("chrome://browser/skin/devtools/debugger-step-out.png");
}
#debugger-controls > toolbarbutton {
#debugger-controls > toolbarbutton,
#sources-controls > toolbarbutton {
margin: 0;
box-shadow: none;
border-radius: 0;
@ -411,11 +385,13 @@
outline-offset: -3px;
}
#debugger-controls > toolbarbutton:last-of-type {
#debugger-controls > toolbarbutton:last-of-type,
#sources-controls > toolbarbutton:last-of-type {
-moz-border-end-width: 0;
}
#debugger-controls {
#debugger-controls,
#sources-controls {
box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset,
0 0 0 1px hsla(210,16%,76%,.15) inset,
0 1px 0 hsla(210,16%,76%,.15);

View File

@ -195,6 +195,7 @@ browser.jar:
skin/classic/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

View File

@ -24,52 +24,29 @@
font-weight: bold;
}
#sources .side-menu-widget-item-checkbox {
-moz-appearance: none;
opacity: 0;
transition: opacity .15s ease-out 0s;
#black-box {
list-style-image: url(debugger-blackbox.png);
-moz-image-region: rect(0px,16px,16px,0px);
}
/* Only show the checkbox when the source is hovered over, is selected, or if it
* is not checked. */
#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item-checkbox:not([checked]) {
opacity: 1;
transition: opacity .15s ease-out 0s;
#black-box[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
#sources .side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-size: 32px 16px;
background-position: -16px 0;
width: 16px;
height: 16px;
border: 0;
}
#sources .side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
#sources .black-boxed {
color: #888;
}
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
#sources .black-boxed > .dbg-breakpoint {
display: none;
}
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
background-image: none;
box-shadow: inset -1px 0 0 #222426;
}
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
background-image: none;
box-shadow: inset 1px 0 0 #222426;
}
@ -145,7 +122,7 @@
/* Sources and breakpoints view */
.dbg-breakpoint {
-moz-margin-start: -14px;
-moz-margin-start: 4px;
}
.dbg-breakpoint-line {
@ -400,7 +377,8 @@
list-style-image: url("chrome://browser/skin/devtools/debugger-step-out.png");
}
#debugger-controls > toolbarbutton {
#debugger-controls > toolbarbutton,
#sources-controls > toolbarbutton {
margin: 0;
box-shadow: none;
border-radius: 0;
@ -409,11 +387,13 @@
outline-offset: -3px;
}
#debugger-controls > toolbarbutton:last-of-type {
#debugger-controls > toolbarbutton:last-of-type,
#sources-controls > toolbarbutton:last-of-type {
-moz-border-end-width: 0;
}
#debugger-controls {
#debugger-controls,
#sources-controls {
box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset,
0 0 0 1px hsla(210,16%,76%,.15) inset,
0 1px 0 hsla(210,16%,76%,.15);

View File

@ -287,6 +287,7 @@ browser.jar:
skin/classic/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/floating-scrollbars.css (devtools/floating-scrollbars.css)
skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

View File

@ -22,54 +22,29 @@
font-weight: bold;
}
#sources .side-menu-widget-item-checkbox {
-moz-appearance: none;
-moz-margin-end: -6px;
padding: 0;
opacity: 0;
transition: opacity .15s ease 0s;
#black-box {
list-style-image: url(debugger-blackbox.png);
-moz-image-region: rect(0px,16px,16px,0px);
}
/* Only show the checkbox when the source is hovered over, is selected, or if it
* is not checked. */
#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
#sources .side-menu-widget-item-checkbox:not([checked]) {
opacity: 1;
transition: opacity .15s ease-out 0s;
#black-box[checked] {
-moz-image-region: rect(0px,32px,16px,16px);
}
#sources .side-menu-widget-item-checkbox > .checkbox-check {
-moz-appearance: none;
background: none;
background-image: url(itemToggle.png);
background-repeat: no-repeat;
background-clip: content-box;
background-size: 32px 16px;
background-position: -16px 0;
width: 16px;
height: 16px;
border: 0;
}
#sources .side-menu-widget-item-checkbox[checked] > .checkbox-check {
background-position: 0 0;
}
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
#sources .black-boxed {
color: #888;
}
#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
#sources .black-boxed > .dbg-breakpoint {
display: none;
}
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
background-image: none;
box-shadow: inset -1px 0 0 #222426;
}
#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
background-image: none;
box-shadow: inset 1px 0 0 #222426;
}
@ -145,7 +120,7 @@
/* Sources and breakpoints view */
.dbg-breakpoint {
-moz-margin-start: -14px;
-moz-margin-start: 4px;
}
.dbg-breakpoint-line {
@ -400,7 +375,8 @@
list-style-image: url("chrome://browser/skin/devtools/debugger-step-out.png");
}
#debugger-controls > toolbarbutton {
#debugger-controls > toolbarbutton,
#sources-controls > toolbarbutton {
margin: 0;
box-shadow: none;
border-radius: 0;
@ -409,11 +385,13 @@
outline-offset: -3px;
}
#debugger-controls > toolbarbutton:last-of-type {
#debugger-controls > toolbarbutton:last-of-type,
#sources-controls > toolbarbutton:last-of-type {
-moz-border-end-width: 0;
}
#debugger-controls {
#debugger-controls,
#sources-controls {
box-shadow: 0 1px 0 hsla(209,29%,72%,.15) inset,
0 0 0 1px hsla(209,29%,72%,.1) inset,
0 0 0 1px hsla(209,29%,72%,.1),

View File

@ -222,6 +222,7 @@ browser.jar:
skin/classic/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
@ -497,6 +498,7 @@ browser.jar:
skin/classic/aero/browser/devtools/debugger-step-in.png (devtools/debugger-step-in.png)
skin/classic/aero/browser/devtools/debugger-step-out.png (devtools/debugger-step-out.png)
skin/classic/aero/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
skin/classic/aero/browser/devtools/debugger-blackbox.png (devtools/debugger-blackbox.png)
skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)

View File

@ -14,6 +14,7 @@ MOZ_THUMB_INTERWORK=toolchain-default
MOZ_FPU=toolchain-default
MOZ_FLOAT_ABI=toolchain-default
MOZ_SOFT_FLOAT=toolchain-default
MOZ_ALIGN=toolchain-default
MOZ_ARG_WITH_STRING(arch,
[ --with-arch=[[type|toolchain-default]]
@ -32,6 +33,7 @@ if test -z "$MOZ_ARCH"; then
MOZ_ARCH=armv7-a
MOZ_FPU=vfp
MOZ_FLOAT_ABI=softfp
MOZ_ALIGN=no
;;
arm-Darwin)
MOZ_ARCH=toolchain-default
@ -159,8 +161,28 @@ no)
;;
esac
case "$MOZ_ALIGN" in
no)
align_flag="-mno-unaligned-access"
;;
yes)
align_flag="-munaligned-access"
;;
*)
align_flag=""
;;
esac
if test -n "$align_flag"; then
_SAVE_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $align_flag"
AC_MSG_CHECKING(whether alignment flag ($align_flag) is supported)
AC_TRY_COMPILE([],[],,align_flag="")
CFLAGS="$_SAVE_CFLAGS"
fi
dnl Use echo to avoid accumulating space characters
all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag`
all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag $align_flag`
if test -n "$all_flags"; then
_SAVE_CFLAGS="$CFLAGS"
CFLAGS="$all_flags"

View File

@ -3986,7 +3986,7 @@ MOZ_PDF_PRINTING=
MOZ_DISABLE_CRYPTOLEGACY=
NSS_DISABLE_DBM=
NECKO_COOKIES=1
NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
NECKO_PROTOCOLS_DEFAULT="about app data file ftp http res viewsource websocket wyciwyg device"
USE_ARM_KUSER=
BUILD_CTYPES=1
MOZ_USE_NATIVE_POPUP_WINDOWS=
@ -8342,6 +8342,11 @@ AC_SUBST(MOZ_POST_DSO_LIB_COMMAND)
AC_SUBST(MOZ_POST_PROGRAM_COMMAND)
AC_SUBST(MOZ_LINKER_EXTRACT)
AC_SUBST(MOZ_JSDOWNLOADS)
if test -n "$MOZ_JSDOWNLOADS"; then
AC_DEFINE(MOZ_JSDOWNLOADS)
fi
dnl ========================================================
dnl = Mac bundle name prefix
dnl ========================================================

View File

@ -166,7 +166,8 @@ this.OperatorAppsRegistry = {
},
appId: undefined,
isBrowser: false,
isPackage: isPackage
isPackage: isPackage,
forceSuccessAck: true
};
if (isPackage) {

View File

@ -2155,6 +2155,20 @@ this.DOMApplicationRegistry = {
queuedPackageDownload: {},
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
// If we are offline, register to run when we'll be online.
if (Services.io.offline) {
let onlineWrapper = {
observe: function(aSubject, aTopic, aData) {
Services.obs.removeObserver(onlineWrapper,
"network:offline-status-changed");
DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
}
}
Services.obs.addObserver(onlineWrapper,
"network:offline-status-changed", false);
return;
}
let cacheDownload = this.queuedDownload[aManifestURL];
if (cacheDownload) {
this.startOfflineCacheDownload(cacheDownload.manifest,
@ -2371,12 +2385,12 @@ this.DOMApplicationRegistry = {
app: appObject,
callback: aInstallSuccessCallback
};
}
if (aData.app.localInstallPath) {
// if it's a local install, there's no content process so just
// ack the install
this.onInstallSuccessAck(app.manifestURL);
}
if (aData.forceSuccessAck) {
// If it's a local install, there's no content process so just
// ack the install.
this.onInstallSuccessAck(app.manifestURL);
}
},

View File

@ -762,10 +762,16 @@ DeviceStorageFile::GetRootDirectoryForType(const nsAString& aStorageType,
// crash reports directory.
else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
f = sDirs->crashes;
} else {
// Not a storage type that we recognize. Return null
return;
}
// in testing, we default all device storage types to a temp directory
if (f && sDirs->temp) {
// In testing, we default all device storage types to a temp directory.
// sDirs->temp will only have been initialized (in InitDirs) if the
// preference device.storage.testing was set to true. We can't test the
// preference directly here, since we may not be on the main thread.
if (sDirs->temp) {
f = sDirs->temp;
}

View File

@ -6,6 +6,7 @@
XPIDL_SOURCES += [
'nsIDOMDesktopNotification.idl',
'nsINotificationStorage.idl',
]
XPIDL_MODULE = 'dom_notification'

View File

@ -0,0 +1,92 @@
/* 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/. */
#include "domstubs.idl"
[scriptable, uuid(fb089720-1c5c-11e3-b773-0800200c9a66)]
interface nsINotificationStorageCallback : nsISupports
{
/**
* Callback function used to pass single notification back
* into C++ land for Notification.get return data.
*
* @param id: a uuid for this notification
* @param title: the notification title
* @param dir: the notification direction,
* possible values are "ltr", "rtl", "auto"
* @param lang: the notification language
* @param body: the notification body
* @param tag: the notification tag
*/
[implicit_jscontext]
void handle(in DOMString id,
in DOMString title,
in DOMString dir,
in DOMString lang,
in DOMString body,
in DOMString tag,
in DOMString icon);
/**
* Callback function used to notify C++ the we have returned
* all notification objects for this Notification.get call.
*/
[implicit_jscontext]
void done();
};
/**
* Interface for notification persistence layer.
*/
[scriptable, uuid(b177b080-2a23-11e3-8224-0800200c9a66)]
interface nsINotificationStorage : nsISupports
{
/**
* Add/replace a notification to the persistence layer.
*
* @param origin: the origin/app of this notification
* @param id: a uuid for this notification
* @param title: the notification title
* @param dir: the notification direction,
* possible values are "ltr", "rtl", "auto"
* @param lang: the notification language
* @param body: the notification body
* @param tag: notification tag, will replace any existing
* notifications with same origin/tag pair
*/
void put(in DOMString origin,
in DOMString id,
in DOMString title,
in DOMString dir,
in DOMString lang,
in DOMString body,
in DOMString tag,
in DOMString icon);
/**
* Retrieve a list of notifications.
*
* @param origin: the origin/app for which to fetch notifications from
* @param tag: used to fetch only a specific tag
* @param callback: nsINotificationStorageCallback, used for
* returning notifications objects
*/
void get(in DOMString origin,
in DOMString tag,
in nsINotificationStorageCallback aCallback);
/**
* Remove a notification from storage.
*
* @param origin: the origin/app to delete the notification from
* @param id: the uuid for the notification to delete
*/
void delete(in DOMString origin,
in DOMString id);
};
%{C++
#define NS_NOTIFICATION_STORAGE_CONTRACTID "@mozilla.org/notificationStorage;1"
%}

View File

@ -189,6 +189,20 @@ Promise::EnabledForScope(JSContext* aCx, JSObject* /* unused */)
prin->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;
}
void
Promise::MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue)
{
MaybeResolveInternal(aCx, aValue);
}
void
Promise::MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue)
{
MaybeRejectInternal(aCx, aValue);
}
static void
EnterCompartment(Maybe<JSAutoCompartment>& aAc, JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue)
@ -229,9 +243,9 @@ Promise::JSCallback(JSContext *aCx, unsigned aArgc, JS::Value *aVp)
PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32());
if (task == PromiseCallback::Resolve) {
promise->MaybeResolve(aCx, value);
promise->MaybeResolveInternal(aCx, value);
} else {
promise->MaybeReject(aCx, value);
promise->MaybeRejectInternal(aCx, value);
}
return true;
@ -300,7 +314,7 @@ Promise::Constructor(const GlobalObject& aGlobal,
Maybe<JSAutoCompartment> ac;
EnterCompartment(ac, cx, value);
promise->MaybeReject(cx, value);
promise->MaybeRejectInternal(cx, value);
}
return promise.forget();
@ -319,7 +333,7 @@ Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
nsRefPtr<Promise> promise = new Promise(window);
Optional<JS::Handle<JS::Value> > value(aCx, aValue);
promise->MaybeResolve(aCx, value);
promise->MaybeResolveInternal(aCx, value);
return promise.forget();
}
@ -336,7 +350,7 @@ Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx,
nsRefPtr<Promise> promise = new Promise(window);
Optional<JS::Handle<JS::Value> > value(aCx, aValue);
promise->MaybeReject(aCx, value);
promise->MaybeRejectInternal(aCx, value);
return promise.forget();
}
@ -441,9 +455,9 @@ Promise::MaybeReportRejected()
}
void
Promise::MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
Promise::MaybeResolveInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
{
if (mResolvePending) {
return;
@ -453,9 +467,9 @@ Promise::MaybeResolve(JSContext* aCx,
}
void
Promise::MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
Promise::MaybeRejectInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aAsynchronous)
{
if (mResolvePending) {
return;

View File

@ -43,6 +43,11 @@ public:
static bool PrefEnabled();
static bool EnabledForScope(JSContext* aCx, JSObject* /* unused */);
void MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue);
void MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue);
// WebIDL
nsPIDOMWindow* GetParentObject() const
@ -114,12 +119,12 @@ private:
// report it to the error console.
void MaybeReportRejected();
void MaybeResolve(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void MaybeReject(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void MaybeResolveInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void MaybeRejectInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,
PromiseTaskSync aSync = AsyncTask);
void ResolveInternal(JSContext* aCx,
const Optional<JS::Handle<JS::Value> >& aValue,

View File

@ -6,14 +6,18 @@
#include "mozilla/dom/Notification.h"
#include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
#include "mozilla/dom/OwningNonNull.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/Preferences.h"
#include "TabChild.h"
#include "nsContentUtils.h"
#include "nsDOMEvent.h"
#include "nsIAlertsService.h"
#include "nsIAppsService.h"
#include "nsIContentPermissionPrompt.h"
#include "nsIDocument.h"
#include "nsINotificationStorage.h"
#include "nsIPermissionManager.h"
#include "nsIUUIDGenerator.h"
#include "nsServiceManagerUtils.h"
#include "nsToolkitCompsCID.h"
#include "nsGlobalWindow.h"
@ -21,12 +25,124 @@
#include "nsIScriptSecurityManager.h"
#ifdef MOZ_B2G
#include "nsIDOMDesktopNotification.h"
#include "nsIAppsService.h"
#endif
namespace mozilla {
namespace dom {
class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback
{
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback)
NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise)
: mCount(0),
mGlobal(aGlobal.Get()),
mWindow(aWindow),
mPromise(aPromise)
{
MOZ_ASSERT(aWindow);
MOZ_ASSERT(aPromise);
JSContext* cx = aGlobal.GetContext();
JSAutoCompartment ac(cx, mGlobal);
mNotifications = JS_NewArrayObject(cx, 0, nullptr);
HoldData();
}
NS_IMETHOD Handle(const nsAString& aID,
const nsAString& aTitle,
const nsAString& aDir,
const nsAString& aLang,
const nsAString& aBody,
const nsAString& aTag,
const nsAString& aIcon,
JSContext* aCx)
{
MOZ_ASSERT(!aID.IsEmpty());
MOZ_ASSERT(!aTitle.IsEmpty());
NotificationOptions options;
options.mDir = Notification::StringToDirection(nsString(aDir));
options.mLang = aLang;
options.mBody = aBody;
options.mTag = aTag;
options.mIcon = aIcon;
nsRefPtr<Notification> notification = Notification::CreateInternal(mWindow,
aID,
aTitle,
options);
JSAutoCompartment ac(aCx, mGlobal);
JS::RootedObject scope(aCx, mGlobal);
JS::RootedObject element(aCx, notification->WrapObject(aCx, scope));
NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
if (!JS_DefineElement(aCx, mNotifications, mCount++,
JS::ObjectValue(*element), nullptr, nullptr, 0)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHOD Done(JSContext* aCx)
{
JSAutoCompartment ac(aCx, mGlobal);
Optional<JS::HandleValue> result(aCx, JS::ObjectValue(*mNotifications));
mPromise->MaybeResolve(aCx, result);
return NS_OK;
}
private:
~NotificationStorageCallback()
{
DropData();
}
void HoldData()
{
mozilla::HoldJSObjects(this);
}
void DropData()
{
mGlobal = nullptr;
mNotifications = nullptr;
mozilla::DropJSObjects(this);
}
uint32_t mCount;
JS::Heap<JSObject *> mGlobal;
nsCOMPtr<nsPIDOMWindow> mWindow;
nsRefPtr<Promise> mPromise;
JS::Heap<JSObject *> mNotifications;
};
NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNotifications)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
tmp->DropData();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
class NotificationPermissionRequest : public nsIContentPermissionRequest,
public PCOMContentPermissionRequestChild,
public nsIRunnable
@ -257,12 +373,15 @@ NotificationTask::Run()
{
switch (mAction) {
case eShow:
return mNotification->ShowInternal();
mNotification->ShowInternal();
break;
case eClose:
return mNotification->CloseInternal();
mNotification->CloseInternal();
break;
default:
MOZ_CRASH("Unexpected action for NotificationTask.");
}
return NS_OK;
}
NS_IMPL_ISUPPORTS1(NotificationObserver, nsIObserver)
@ -283,50 +402,103 @@ NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
return NS_OK;
}
Notification::Notification(const nsAString& aTitle, const nsAString& aBody,
Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl)
: mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
: mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false)
{
SetIsDOMBinding();
}
// static
already_AddRefed<Notification>
Notification::Constructor(const GlobalObject& aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOptions,
ErrorResult& aRv)
{
nsString tag;
if (aOptions.mTag.WasPassed()) {
tag.Append(NS_LITERAL_STRING("tag:"));
tag.Append(aOptions.mTag.Value());
} else {
tag.Append(NS_LITERAL_STRING("notag:"));
tag.AppendInt(sCount++);
}
nsRefPtr<Notification> notification = new Notification(aTitle,
aOptions.mBody,
aOptions.mDir,
aOptions.mLang,
tag,
aOptions.mIcon);
MOZ_ASSERT(NS_IsMainThread());
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
MOZ_ASSERT(window, "Window should not be null.");
notification->BindToOwner(window);
nsRefPtr<Notification> notification = CreateInternal(window,
EmptyString(),
aTitle,
aOptions);
// Queue a task to show the notification.
nsCOMPtr<nsIRunnable> showNotificationTask =
new NotificationTask(notification, NotificationTask::eShow);
NS_DispatchToMainThread(showNotificationTask);
NS_DispatchToCurrentThread(showNotificationTask);
// Persist the notification.
nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
nsString origin;
aRv = GetOrigin(window, origin);
if (aRv.Failed()) {
return nullptr;
}
nsString id;
notification->GetID(id);
aRv = notificationStorage->Put(origin,
id,
aTitle,
DirectionToString(aOptions.mDir),
aOptions.mLang,
aOptions.mBody,
aOptions.mTag,
aOptions.mIcon);
if (aRv.Failed()) {
return nullptr;
}
return notification.forget();
}
nsresult
already_AddRefed<Notification>
Notification::CreateInternal(nsPIDOMWindow* aWindow,
const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions)
{
nsString id;
if (!aID.IsEmpty()) {
id = aID;
} else {
nsCOMPtr<nsIUUIDGenerator> uuidgen =
do_GetService("@mozilla.org/uuid-generator;1");
NS_ENSURE_TRUE(uuidgen, nullptr);
nsID uuid;
nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid);
NS_ENSURE_SUCCESS(rv, nullptr);
char buffer[NSID_LENGTH];
uuid.ToProvidedString(buffer);
NS_ConvertASCIItoUTF16 convertedID(buffer);
id = convertedID;
}
nsRefPtr<Notification> notification = new Notification(id,
aTitle,
aOptions.mBody,
aOptions.mDir,
aOptions.mLang,
aOptions.mTag,
aOptions.mIcon);
notification->BindToOwner(aWindow);
return notification.forget();
}
void
Notification::ShowInternal()
{
nsCOMPtr<nsIAlertsService> alertService =
@ -337,7 +509,8 @@ Notification::ShowInternal()
NotificationPermission::Granted || !alertService) {
// We do not have permission to show a notification or alert service
// is not available.
return DispatchTrustedEvent(NS_LITERAL_STRING("error"));
DispatchTrustedEvent(NS_LITERAL_STRING("error"));
return;
}
nsresult rv;
@ -345,17 +518,19 @@ Notification::ShowInternal()
if (mIconUrl.Length() > 0) {
// Resolve image URL against document base URI.
nsIDocument* doc = GetOwner()->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
NS_ENSURE_TRUE(baseUri, NS_ERROR_UNEXPECTED);
nsCOMPtr<nsIURI> srcUri;
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
mIconUrl, doc, baseUri);
NS_ENSURE_SUCCESS(rv, rv);
if (srcUri) {
nsAutoCString src;
srcUri->GetSpec(src);
absoluteUrl = NS_ConvertUTF8toUTF16(src);
if (doc) {
nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
if (baseUri) {
nsCOMPtr<nsIURI> srcUri;
rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
mIconUrl, doc, baseUri);
if (NS_SUCCEEDED(rv)) {
nsAutoCString src;
srcUri->GetSpec(src);
absoluteUrl = NS_ConvertUTF8toUTF16(src);
}
}
}
}
@ -363,7 +538,7 @@ Notification::ShowInternal()
nsString alertName;
rv = GetAlertName(alertName);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_SUCCESS_VOID(rv);
#ifdef MOZ_B2G
nsCOMPtr<nsIAppNotificationService> appNotifier =
@ -375,23 +550,26 @@ Notification::ShowInternal()
if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
nsString manifestUrl = EmptyString();
appsService->GetManifestURLByLocalId(appId, manifestUrl);
mozilla::AutoSafeJSContext cx;
JS::RootedValue val(cx);
AppNotificationServiceOptions ops;
ops.mTextClickable = true;
ops.mManifestURL = manifestUrl;
ops.mId = alertName;
ops.mDir = DirectionToString(mDir);
ops.mLang = mLang;
rv = appsService->GetManifestURLByLocalId(appId, manifestUrl);
if (NS_SUCCEEDED(rv)) {
mozilla::AutoSafeJSContext cx;
JS::RootedValue val(cx);
AppNotificationServiceOptions ops;
ops.mTextClickable = true;
ops.mManifestURL = manifestUrl;
ops.mId = alertName;
ops.mDir = DirectionToString(mDir);
ops.mLang = mLang;
if (!ops.ToObject(cx, JS::NullPtr(), &val)) {
NS_WARNING("Converting dict to object failed!");
return NS_ERROR_FAILURE;
if (!ops.ToObject(cx, JS::NullPtr(), &val)) {
NS_WARNING("Converting dict to object failed!");
return;
}
appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
observer, val);
return;
}
return appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
observer, val);
}
}
#endif
@ -400,9 +578,9 @@ Notification::ShowInternal()
// nsIObserver. Thus the cookie must be unique to differentiate observers.
nsString uniqueCookie = NS_LITERAL_STRING("notification:");
uniqueCookie.AppendInt(sCount++);
return alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
uniqueCookie, observer, alertName,
DirectionToString(mDir), mLang);
alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
uniqueCookie, observer, alertName,
DirectionToString(mDir), mLang);
}
void
@ -490,6 +668,47 @@ Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv)
}
}
already_AddRefed<Promise>
Notification::Get(const GlobalObject& aGlobal,
const GetNotificationOptions& aFilter,
ErrorResult& aRv)
{
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
MOZ_ASSERT(window);
nsIDocument* doc = window->GetExtantDoc();
if (!doc) {
aRv.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
nsString origin;
aRv = GetOrigin(window, origin);
if (aRv.Failed()) {
return nullptr;
}
nsresult rv;
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
nsRefPtr<Promise> promise = new Promise(window);
nsCOMPtr<nsINotificationStorageCallback> callback =
new NotificationStorageCallback(aGlobal, window, promise);
nsString tag = aFilter.mTag.WasPassed() ?
aFilter.mTag.Value() :
EmptyString();
aRv = notificationStorage->Get(origin, tag, callback);
if (aRv.Failed()) {
return nullptr;
}
return promise.forget();
}
bool
Notification::PrefEnabled()
{
@ -511,22 +730,61 @@ Notification::Close()
NS_DispatchToMainThread(showNotificationTask);
}
nsresult
void
Notification::CloseInternal()
{
if (!mIsClosed) {
nsresult rv;
// Don't bail out if notification storage fails, since we still
// want to send the close event through the alert service.
nsCOMPtr<nsINotificationStorage> notificationStorage =
do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
if (notificationStorage) {
nsString origin;
rv = GetOrigin(GetOwner(), origin);
if (NS_SUCCEEDED(rv)) {
notificationStorage->Delete(origin, mID);
}
}
nsCOMPtr<nsIAlertsService> alertService =
do_GetService(NS_ALERTSERVICE_CONTRACTID);
if (alertService) {
nsString alertName;
nsresult rv = GetAlertName(alertName);
NS_ENSURE_SUCCESS(rv, rv);
rv = alertService->CloseAlert(alertName);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetAlertName(alertName);
if (NS_SUCCEEDED(rv)) {
alertService->CloseAlert(alertName);
}
}
}
}
nsresult
Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
{
MOZ_ASSERT(aWindow);
nsresult rv;
nsIDocument* doc = aWindow->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsIPrincipal* principal = doc->NodePrincipal();
NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
uint16_t appStatus = principal->GetAppStatus();
uint32_t appId = principal->GetAppId();
if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED ||
appId == nsIScriptSecurityManager::NO_APP_ID ||
appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// If we are in "app code", use manifest URL as unique origin since
// multiple apps can share the same origin but not same notifications.
nsCOMPtr<nsIAppsService> appsService =
do_GetService("@mozilla.org/AppsService;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
appsService->GetManifestURLByLocalId(appId, aOrigin);
}
return NS_OK;
}
@ -534,20 +792,18 @@ Notification::CloseInternal()
nsresult
Notification::GetAlertName(nsString& aAlertName)
{
// Get the notification name that is unique per origin + tag.
// The name of the alert is of the form origin#tag
nsPIDOMWindow* owner = GetOwner();
NS_ENSURE_TRUE(owner, NS_ERROR_UNEXPECTED);
nsIDocument* doc = owner->GetExtantDoc();
NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
nsresult rv = nsContentUtils::GetUTFOrigin(doc->NodePrincipal(),
aAlertName);
// Get the notification name that is unique per origin + tag/ID.
// The name of the alert is of the form origin#tag/ID.
nsresult rv = GetOrigin(GetOwner(), aAlertName);
NS_ENSURE_SUCCESS(rv, rv);
aAlertName.AppendLiteral("#");
aAlertName.Append(mTag);
if (!mTag.IsEmpty()) {
aAlertName.Append(NS_LITERAL_STRING("tag:"));
aAlertName.Append(mTag);
} else {
aAlertName.Append(NS_LITERAL_STRING("notag:"));
aAlertName.Append(mID);
}
return NS_OK;
}

View File

@ -10,31 +10,37 @@
#include "nsDOMEventTargetHelper.h"
#include "nsIObserver.h"
#include "nsCycleCollectionParticipant.h"
namespace mozilla {
namespace dom {
class NotificationObserver;
class Promise;
class Notification : public nsDOMEventTargetHelper
{
friend class NotificationTask;
friend class NotificationPermissionRequest;
friend class NotificationObserver;
friend class NotificationStorageCallback;
public:
IMPL_EVENT_HANDLER(click)
IMPL_EVENT_HANDLER(show)
IMPL_EVENT_HANDLER(error)
IMPL_EVENT_HANDLER(close)
Notification(const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl);
static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
const nsAString& aTitle,
const NotificationOptions& aOption,
ErrorResult& aRv);
void GetTitle(nsString& aRetval)
void GetID(nsAString& aRetval) {
aRetval = mID;
}
void GetTitle(nsAString& aRetval)
{
aRetval = mTitle;
}
@ -44,24 +50,22 @@ public:
return mDir;
}
void GetLang(nsString& aRetval)
void GetLang(nsAString& aRetval)
{
aRetval = mLang;
}
void GetBody(nsString& aRetval)
void GetBody(nsAString& aRetval)
{
aRetval = mBody;
}
void GetTag(nsString& aRetval)
void GetTag(nsAString& aRetval)
{
if (StringBeginsWith(mTag, NS_LITERAL_STRING("tag:"))) {
aRetval = Substring(mTag, 4);
}
aRetval = mTag;
}
void GetIcon(nsString& aRetval)
void GetIcon(nsAString& aRetval)
{
aRetval = mIconUrl;
}
@ -73,6 +77,10 @@ public:
static NotificationPermission GetPermission(const GlobalObject& aGlobal,
ErrorResult& aRv);
static already_AddRefed<Promise> Get(const GlobalObject& aGlobal,
const GetNotificationOptions& aFilter,
ErrorResult& aRv);
void Close();
static bool PrefEnabled();
@ -85,8 +93,17 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
protected:
nsresult ShowInternal();
nsresult CloseInternal();
Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
NotificationDirection aDir, const nsAString& aLang,
const nsAString& aTag, const nsAString& aIconUrl);
static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow,
const nsAString& aID,
const nsAString& aTitle,
const NotificationOptions& aOptions);
void ShowInternal();
void CloseInternal();
static NotificationPermission GetPermissionInternal(nsISupports* aGlobal,
ErrorResult& rv);
@ -103,8 +120,22 @@ protected:
}
}
static const NotificationDirection StringToDirection(const nsAString& aDirection)
{
if (aDirection.EqualsLiteral("ltr")) {
return NotificationDirection::Ltr;
}
if (aDirection.EqualsLiteral("rtl")) {
return NotificationDirection::Rtl;
}
return NotificationDirection::Auto;
}
static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
nsresult GetAlertName(nsString& aAlertName);
nsString mID;
nsString mTitle;
nsString mBody;
NotificationDirection mDir;

View File

@ -0,0 +1,270 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = [];
const DEBUG = false;
function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }
const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageListenerManager");
XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
return new TextEncoder();
});
XPCOMUtils.defineLazyGetter(this, "gDecoder", function() {
return new TextDecoder();
});
const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
const NOTIFICATION_STORE_PATH =
OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
let NotificationDB = {
init: function() {
this.notifications = {};
this.byTag = {};
this.loaded = false;
this.tasks = []; // read/write operation queue
this.runningTask = false;
ppmm.addMessageListener("Notification:Save", this);
ppmm.addMessageListener("Notification:Delete", this);
ppmm.addMessageListener("Notification:GetAll", this);
},
// Attempt to read notification file, if it's not there we will create it.
load: function(callback) {
var promise = OS.File.read(NOTIFICATION_STORE_PATH);
promise.then(
function onSuccess(data) {
try {
this.notifications = JSON.parse(gDecoder.decode(data));
} catch (e) {
if (DEBUG) { debug("Unable to parse file data " + e); }
}
this.loaded = true;
callback && callback();
}.bind(this),
// If read failed, we assume we have no notifications to load.
function onFailure(reason) {
this.loaded = true;
this.createStore(callback);
}.bind(this)
);
},
// Creates the notification directory.
createStore: function(callback) {
var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
ignoreExisting: true
});
promise.then(
function onSuccess() {
this.createFile(callback);
}.bind(this),
function onFailure(reason) {
if (DEBUG) { debug("Directory creation failed:" + reason); }
callback && callback();
}
);
},
// Creates the notification file once the directory is created.
createFile: function(callback) {
var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true});
promise.then(
function onSuccess(handle) {
callback && callback();
},
function onFailure(reason) {
if (DEBUG) { debug("File creation failed:" + reason); }
callback && callback();
}
);
},
// Save current notifications to the file.
save: function(callback) {
var data = gEncoder.encode(JSON.stringify(this.notifications));
var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data);
promise.then(
function onSuccess() {
callback && callback();
},
function onFailure(reason) {
if (DEBUG) { debug("Save failed:" + reason); }
callback && callback();
}
);
},
// Helper function: callback will be called once file exists and/or is loaded.
ensureLoaded: function(callback) {
if (!this.loaded) {
this.load(callback);
} else {
callback();
}
},
receiveMessage: function(message) {
if (DEBUG) { debug("Received message:" + message.name); }
switch (message.name) {
case "Notification:GetAll":
this.queueTask("getall", message.data, function(notifications) {
message.target.sendAsyncMessage("Notification:GetAll:Return:OK", {
requestID: message.data.requestID,
notifications: notifications
});
});
break;
case "Notification:Save":
this.queueTask("save", message.data, function() {
message.target.sendAsyncMessage("Notification:Save:Return:OK", {
requestID: message.data.requestID
});
});
break;
case "Notification:Delete":
this.queueTask("delete", message.data, function() {
message.target.sendAsyncMessage("Notification:Delete:Return:OK", {
requestID: message.data.requestID
});
});
break;
default:
if (DEBUG) { debug("Invalid message name" + message.name); }
}
},
// We need to make sure any read/write operations are atomic,
// so use a queue to run each operation sequentially.
queueTask: function(operation, data, callback) {
if (DEBUG) { debug("Queueing task: " + operation); }
this.tasks.push({
operation: operation,
data: data,
callback: callback
});
// Only run immediately if we aren't currently running another task.
if (!this.runningTask) {
if (DEBUG) { dump("Task queue was not running, starting now..."); }
this.runNextTask();
}
},
runNextTask: function() {
if (this.tasks.length === 0) {
if (DEBUG) { dump("No more tasks to run, queue depleted"); }
this.runningTask = false;
return;
}
this.runningTask = true;
// Always make sure we are loaded before performing any read/write tasks.
this.ensureLoaded(function() {
var task = this.tasks.shift();
// Wrap the task callback to make sure we immediately
// run the next task after running the original callback.
var wrappedCallback = function() {
if (DEBUG) { debug("Finishing task: " + task.operation); }
task.callback.apply(this, arguments);
this.runNextTask();
}.bind(this);
switch (task.operation) {
case "getall":
this.taskGetAll(task.data, wrappedCallback);
break;
case "save":
this.taskSave(task.data, wrappedCallback);
break;
case "delete":
this.taskDelete(task.data, wrappedCallback);
break;
}
}.bind(this));
},
taskGetAll: function(data, callback) {
if (DEBUG) { debug("Task, getting all"); }
var origin = data.origin;
var notifications = [];
// Grab only the notifications for specified origin.
for (var i in this.notifications[origin]) {
notifications.push(this.notifications[origin][i]);
}
callback(notifications);
},
taskSave: function(data, callback) {
if (DEBUG) { debug("Task, saving"); }
var origin = data.origin;
var notification = data.notification;
if (!this.notifications[origin]) {
this.notifications[origin] = {};
this.byTag[origin] = {};
}
// We might have existing notification with this tag,
// if so we need to remove it before saving the new one.
if (notification.tag && this.byTag[origin][notification.tag]) {
var oldNotification = this.byTag[origin][notification.tag];
delete this.notifications[origin][oldNotification.id];
this.byTag[origin][notification.tag] = notification;
}
this.notifications[origin][notification.id] = notification;
this.save(callback);
},
taskDelete: function(data, callback) {
if (DEBUG) { debug("Task, deleting"); }
var origin = data.origin;
var id = data.id;
if (!this.notifications[origin]) {
if (DEBUG) { debug("No notifications found for origin: " + origin); }
return;
}
// Make sure we can find the notification to delete.
var oldNotification = this.notifications[origin][id];
if (!oldNotification) {
if (DEBUG) { debug("No notification found with id: " + id); }
return;
}
if (oldNotification.tag) {
delete this.byTag[origin][oldNotification.tag];
}
delete this.notifications[origin][id];
this.save(callback);
}
};
NotificationDB.init();

View File

@ -0,0 +1,174 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const DEBUG = false;
function debug(s) { dump("-*- NotificationStorage.js: " + s + "\n"); }
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const NOTIFICATIONSTORAGE_CID = "{37f819b0-0b5c-11e3-8ffd-0800200c9a66}";
const NOTIFICATIONSTORAGE_CONTRACTID = "@mozilla.org/notificationStorage;1";
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
"@mozilla.org/childprocessmessagemanager;1",
"nsIMessageSender");
function NotificationStorage() {
// cache objects
this._notifications = {};
this._byTag = {};
this._cached = false;
this._requests = {};
this._requestCount = 0;
// Register for message listeners.
cpmm.addMessageListener("Notification:GetAll:Return:OK", this);
}
NotificationStorage.prototype = {
put: function(origin, id, title, dir, lang, body, tag, icon) {
if (DEBUG) { debug("PUT: " + id + ": " + title); }
var notification = {
id: id,
title: title,
dir: dir,
lang: lang,
body: body,
tag: tag,
icon: icon
};
this._notifications[id] = notification;
if (tag) {
// We might have existing notification with this tag,
// if so we need to remove it from our cache.
if (this._byTag[tag]) {
var oldNotification = this._byTag[tag];
delete this._notifications[oldNotification.id];
}
this._byTag[tag] = notification;
};
cpmm.sendAsyncMessage("Notification:Save", {
origin: origin,
notification: notification
});
},
get: function(origin, tag, callback) {
if (DEBUG) { debug("GET: " + tag); }
if (this._cached) {
this._fetchFromCache(tag, callback);
} else {
this._fetchFromDB(origin, tag, callback);
}
},
delete: function(origin, id) {
if (DEBUG) { debug("DELETE: " + id); }
var notification = this._notifications[id];
if (notification) {
if (notification.tag) {
delete this._byTag[notification.tag];
}
delete this._notifications[id];
}
cpmm.sendAsyncMessage("Notification:Delete", {
origin: origin,
id: id
});
},
receiveMessage: function(message) {
switch (message.name) {
case "Notification:GetAll:Return:OK":
var request = this._requests[message.data.requestID];
delete this._requests[message.data.requestID];
this._populateCache(message.data.notifications);
this._fetchFromCache(request.tag, request.callback);
break;
default:
if (DEBUG) debug("Unrecognized message: " + message.name);
break;
}
},
_fetchFromDB: function(origin, tag, callback) {
var request = {
origin: origin,
tag: tag,
callback: callback
};
var requestID = this._requestCount++;
this._requests[requestID] = request;
cpmm.sendAsyncMessage("Notification:GetAll", {
origin: origin,
requestID: requestID
});
},
_fetchFromCache: function(tag, callback) {
var notifications = [];
// If a tag was specified and we have a notification
// with this tag, return that. If no tag was specified
// simple return all stored notifications.
if (tag && this._byTag[tag]) {
notifications.push(this._byTag[tag]);
} else if (!tag) {
for (var id in this._notifications) {
notifications.push(this._notifications[id]);
}
}
// Pass each notification back separately.
notifications.forEach(function(notification) {
try {
callback.handle(notification.id,
notification.title,
notification.dir,
notification.lang,
notification.body,
notification.tag,
notification.icon);
} catch (e) {
if (DEBUG) { debug("Error calling callback handle: " + e); }
}
});
try {
callback.done();
} catch (e) {
if (DEBUG) { debug("Error calling callback done: " + e); }
}
},
_populateCache: function(notifications) {
notifications.forEach(function(notification) {
this._notifications[notification.id] = notification;
if (notification.tag) {
this._byTag[notification.tag] = notification;
}
}.bind(this));
this._cached = true;
},
classID : Components.ID(NOTIFICATIONSTORAGE_CID),
contractID : NOTIFICATIONSTORAGE_CONTRACTID,
QueryInterface: XPCOMUtils.generateQI([Ci.nsINotificationStorage,
Ci.nsIMessageListener]),
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NotificationStorage]);

View File

@ -0,0 +1,3 @@
# NotificationStorage.js
component {37f819b0-0b5c-11e3-8ffd-0800200c9a66} NotificationStorage.js
contract @mozilla.org/notificationStorage;1 {37f819b0-0b5c-11e3-8ffd-0800200c9a66}

View File

@ -6,6 +6,15 @@
MODULE = 'dom'
EXTRA_COMPONENTS += [
'NotificationStorage.js',
'NotificationStorage.manifest',
]
EXTRA_JS_MODULES += [
'NotificationDB.jsm'
]
EXPORTS.mozilla.dom += [
'DesktopNotification.h',
'Notification.h',

View File

@ -29,7 +29,3 @@ DIRS += [
if CONFIG['MOZ_GAMEPAD']:
DIRS += ['gamepad']
#needs IPC support, also tests do not run successfully in Firefox for now
#if CONFIG['MOZ_BUILD_APP'] != 'mobile':
# DIRS += ['notification']

View File

@ -0,0 +1,81 @@
var MockServices = (function () {
"use strict";
const MOCK_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
.ID("{48068bc2-40ab-4904-8afd-4cdfb3a385f3}");
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
const MOCK_SYSTEM_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
.ID("{e86d888c-e41b-4b78-9104-2f2742a532de}");
const SYSTEM_ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/system-alerts-service;1";
var registrar = SpecialPowers.wrap(SpecialPowers.Components).manager
.QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar);
var activeNotifications = Object.create(null);
var mockAlertsService = {
showAlertNotification: function(imageUrl, title, text, textClickable,
cookie, alertListener, name) {
var listener = SpecialPowers.wrap(alertListener);
activeNotifications[name] = {
listener: listener,
cookie: cookie
};
// fake async alert show event
setTimeout(function () {
listener.observe(null, "alertshow", cookie);
}, 100);
// ?? SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie);
},
showAppNotification: function(imageUrl, title, text, textClickable,
manifestURL, alertListener, name) {
this.showAlertNotification(imageUrl, title, text, textClickable, "", alertListener, name);
},
closeAlert: function(name) {
var notification = activeNotifications[name];
if (notification) {
notification.listener.observe(null, "alertfinished", notification.cookie);
delete activeNotifications[name];
}
},
QueryInterface: function(aIID) {
if (SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService)) {
return this;
}
throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
},
createInstance: function(aOuter, aIID) {
if (aOuter != null) {
throw SpecialPowers.Components.results.NS_ERROR_NO_AGGREGATION;
}
return this.QueryInterface(aIID);
}
};
mockAlertsService = SpecialPowers.wrapCallbackObject(mockAlertsService);
// MockServices API
return {
register: function () {
registrar.registerFactory(MOCK_ALERTS_CID, "alerts service",
ALERTS_SERVICE_CONTRACT_ID,
mockAlertsService);
registrar.registerFactory(MOCK_SYSTEM_ALERTS_CID, "system alerts service",
SYSTEM_ALERTS_SERVICE_CONTRACT_ID,
mockAlertsService);
},
unregister: function () {
registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService);
registrar.unregisterFactory(MOCK_SYSTEM_ALERTS_CID, mockAlertsService);
},
};
})();

View File

@ -0,0 +1,73 @@
var NotificationTest = (function () {
"use strict";
function info(msg, name) {
SimpleTest.info("::Notification Tests::" + (name || ""), msg);
}
function setup_testing_env() {
SimpleTest.waitForExplicitFinish();
// turn on testing pref (used by notification.cpp, and mock the alerts
SpecialPowers.setBoolPref("notification.prompt.testing", true);
}
function teardown_testing_env() {
SimpleTest.finish();
}
function executeTests(tests, callback) {
// context is `this` object in test functions
// it can be used to track data between tests
var context = {};
(function executeRemainingTests(remainingTests) {
if (!remainingTests.length) {
return callback();
}
var nextTest = remainingTests.shift();
var finishTest = executeRemainingTests.bind(null, remainingTests);
var startTest = nextTest.call.bind(nextTest, context, finishTest);
try {
startTest();
// if no callback was defined for test function,
// we must manually invoke finish to continue
if (nextTest.length === 0) {
finishTest();
}
} catch (e) {
ok(false, "Test threw exception!");
finishTest();
}
})(tests);
}
// NotificationTest API
return {
run: function (tests, callback) {
setup_testing_env();
addLoadEvent(function () {
executeTests(tests, function () {
teardown_testing_env();
callback && callback();
});
});
},
allowNotifications: function () {
SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
},
denyNotifications: function () {
SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
},
clickNotification: function (notification) {
// TODO: how??
},
info: info
};
})();

View File

@ -0,0 +1,6 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.

View File

@ -1,10 +1,7 @@
[DEFAULT]
support-files =
create_notification.html
notification_common.js
MockServices.js
NotificationTest.js
[test_basic_notification.html]
[test_basic_notification_click.html]
[test_leak_windowClose.html]
[test_notification_tag.html]
[test_web_notifications.html]
[test_notification_basics.html]
[test_notification_storage.html]

View File

@ -0,0 +1,115 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Notification Basics</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="MockServices.js"></script>
<script type="text/javascript" src="NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script type="text/javascript">
var info = NotificationTest.info;
var steps = [
function () {
info("Test notification spec");
ok(Notification, "Notification constructor exists");
ok(Notification.permission, "Notification.permission exists");
ok(Notification.requestPermission, "Notification.requestPermission exists");
ok(Notification.get, "Notification.get exists");
},
function () {
info("Test blank requestPermission");
Notification.requestPermission();
},
function (done) {
info("Test requestPermission deny");
NotificationTest.denyNotifications();
Notification.requestPermission(function(perm) {
is(perm, "denied", "Permission should be denied.");
is(Notification.permission, "denied", "Permission should be denied.");
done();
});
},
function (done) {
info("Test requestPermission grant");
NotificationTest.allowNotifications();
Notification.requestPermission(function (perm) {
is(perm, "granted", "Permission should be granted.");
is(Notification.permission, "granted", "Permission should be granted");
done();
});
},
function () {
info("Test invalid requestPermission");
try {
Notification.requestPermission({});
ok(false, "Non callable arg to requestPermission should throw");
} catch (e) {
ok(true, "Non callable arg to requestPermission should throw");
}
},
function (done) {
info("Test create notification");
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag",
icon: "icon.png"
};
var notification = new Notification("This is a title", options);
ok(notification, "Notification exists");
is(notification.onclick, null, "onclick() should be null");
is(notification.onshow, null, "onshow() should be null");
is(notification.onerror, null, "onerror() should be null");
is(notification.onclose, null, "onclose() should be null");
is(typeof notification.close, "function", "close() should exist");
is(notification.dir, options.dir, "auto should get set");
is(notification.lang, options.lang, "lang should get set");
is(notification.body, options.body, "body should get set");
is(notification.tag, options.tag, "tag should get set");
is(notification.icon, options.icon, "icon should get set");
// store notification in test context
this.notification = notification;
notification.onshow = function () {
ok(true, "onshow handler should be called");
done();
};
},
function (done) {
info("Test closing a notification");
var notification = this.notification;
notification.onclose = function () {
ok(true, "onclose handler should be called");
done();
};
notification.close();
},
];
MockServices.register();
NotificationTest.run(steps, function () {
MockServices.unregister();
});
</script>
</body>
</html>

View File

@ -0,0 +1,132 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Notification Basics</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="MockServices.js"></script>
<script type="text/javascript" src="NotificationTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script type="text/javascript">
function deleteAllNotifications() {
var promise = Notification.get();
promise.then(function (notifications) {
notifications.forEach(function(notification) {
notification.close();
});
});
}
var info = NotificationTest.info;
var steps = [
function (done) {
info("Test that Notifcation.get fulfills the promise");
var promise = Notification.get();
ok(promise.then, "should return a promise");
// Create a new notification to make sure
// Notification.get() works while creating
var notification = new Notification("this is a test");
promise.then(function () {
ok(true, "promise should be fulfilled");
done();
});
},
deleteAllNotifications,
function (done) {
info("Test adding a notification, and making sure get returns it");
NotificationTest.allowNotifications();
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag",
icon: "icon.png"
};
var notification = new Notification("This is a title", options);
var promise = Notification.get();
promise.then(function (notifications) {
ok(notifications.length, "should return notifications");
for (var i = 0; i < notifications.length; i++) {
var notification = notifications[i];
if (notification.tag === options.tag) {
ok(true, "should contain newly created notification");
for (var key in options) {
is(notification[key], options[key], key + " property should match");
}
notification.close();
return;
}
}
ok(false, "should contain newly created notification");
notification.close();
});
notification.onclose = done;
},
function (done) {
info("Testing fetching notification by tag filter");
var n1 = new Notification("title1", {tag: "tag1"});
var n2 = new Notification("title2", {tag: "tag2"});
var n3 = new Notification("title3", {tag: "tag3"});
var promise = Notification.get({tag: "tag3"});
promise.then(function (notifications) {
var notification = notifications[0];
is(notifications.length, 1, "should return 1 notification");
is(notifications[0].title, "title3", "titles should match");
is(notifications[0].tag, "tag3", "tags should match");
var closeCount = 0;
var waitForAll = function () {
if (++closeCount >= 3) {
done();
}
};
n1.onclose = waitForAll;
n2.onclose = waitForAll;
n3.onclose = waitForAll;
n1.close();
n2.close();
n3.close();
});
},
deleteAllNotifications,
function (done) {
info("Testing fetching no notifications");
var promise = Notification.get();
promise.then(function (notifications) {
is(notifications.length, 0, "should return 0 notifications");
done();
});
},
function (done) {
info("Testing fetching multiple notifications");
var n1 = new Notification("title1");
var n2 = new Notification("title2");
var n3 = new Notification("title3");
var promise = Notification.get();
promise.then(function (notifications) {
is(notifications.length, 3, "should return 2 notifications");
done();
});
}
];
MockServices.register();
NotificationTest.run(steps, function () {
MockServices.unregister();
});
</script>
</body>
</html>

View File

@ -1,100 +0,0 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=782211
-->
<head>
<title>Bug 782211</title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="notification_common.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782211">Bug 782211</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
</pre>
<script type="text/javascript">
if (window.Notification) {
SimpleTest.waitForExplicitFinish();
function showNotifications() {
// Make sure callback is called.
Notification.requestPermission(function(perm) {
is(perm, "granted", "Permission should be granted.");
is(Notification.permission, "granted", "Permission should be granted.");
callbackCalled();
});
// Make sure nothing bad happens when requestPermission is called without a callback.
Notification.requestPermission();
try {
Notification.requestPermission({});
ok(false, "Non callable arugment to request permission should throw exception.");
} catch (ex) {
ok(true, "Non callable arugment to request permission should throw exception.");
}
var title = "This is a title";
var notification = new Notification(title);
is(notification.title, title, "Title should be set");
is(notification.dir, "auto", "Dir should default to 'auto'");
is(notification.lang, "", "Lang should not be set");
is(notification.body, "", "Body should not be set");
is(notification.tag, "", "Tag should not be set");
var options = {
dir: "auto",
lang: "",
body: "This is a notification body",
tag: "sometag"
};
var notification = new Notification(title, options);
is(notification.title, title, "Title should be set");
is(notification.dir, options.dir, "Dir should be set");
is(notification.lang, options.lang, "Lang should be set");
is(notification.body, options.body, "Body should be set");
is(notification.tag, options.tag, "Tag should be set");
notification.onclose = function() {
ok(true, "Notification should be closed.");
callbackCalled();
};
notification.onshow = function() {
ok(true, "Notification should be shown.");
notification.close();
callbackCalled();
};
notification.onerror = function() {
ok(false, "Failed to show notification.");
reset_notifications();
SimpleTest.finish();
};
var numCallbacksCalled = 0;
function callbackCalled() {
numCallbacksCalled++;
if (numCallbacksCalled == 3) {
reset_notifications();
SimpleTest.finish();
}
}
}
setup_notifications(true, true, showNotifications);
} else {
ok(true, "Notifications are not enabled on the platform.");
}
</script>
</body>
</html>

12
dom/webidl/AppInfo.webidl Normal file
View File

@ -0,0 +1,12 @@
/* 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/. */
/**
* This dictionnary holds the parameters supporting the app:// protocol.
*/
dictionary AppInfo
{
DOMString path = "";
boolean isCoreApp = false;
};

View File

@ -28,6 +28,7 @@ interface DummyInterface : EventTarget {
void WifiOptions(optional WifiCommandOptions arg1,
optional WifiResultOptions arg2);
void AppNotificationServiceOptions(optional AppNotificationServiceOptions arg);
void AppInfo(optional AppInfo arg1);
};
interface DummyInterfaceWorkers {

View File

@ -19,6 +19,9 @@ interface Notification : EventTarget {
[Throws]
static void requestPermission(optional NotificationPermissionCallback permissionCallback);
[Throws]
static Promise get(optional GetNotificationOptions filter);
attribute EventHandler onclick;
attribute EventHandler onshow;
@ -52,10 +55,14 @@ dictionary NotificationOptions {
NotificationDirection dir = "auto";
DOMString lang = "";
DOMString body = "";
DOMString tag;
DOMString tag = "";
DOMString icon = "";
};
dictionary GetNotificationOptions {
DOMString tag;
};
enum NotificationPermission {
"default",
"denied",

View File

@ -19,6 +19,7 @@ WEBIDL_FILES = [
'AbstractWorker.webidl',
'AnalyserNode.webidl',
'AnimationEvent.webidl',
'AppInfo.webidl',
'AppNotificationServiceOptions.webidl',
'ArchiveReader.webidl',
'ArchiveRequest.webidl',

View File

@ -173,14 +173,14 @@ static float gYStationarySizeMultiplier = 2.5f;
/**
* The time period in ms that throttles mozbrowserasyncscroll event.
* Default is 100ms if there is no "apzc.asyncscroll.throttle" in preference.
* Default is 100ms if there is no "apz.asyncscroll.throttle" in preference.
*/
static int gAsyncScrollThrottleTime = 100;
/**
* The timeout in ms for mAsyncScrollTimeoutTask delay task.
* Default is 300ms if there is no "apzc.asyncscroll.timeout" in preference.
* Default is 300ms if there is no "apz.asyncscroll.timeout" in preference.
*/
static int gAsyncScrollTimeout = 300;
@ -236,21 +236,21 @@ AsyncPanZoomController::InitializeGlobalState()
return;
sInitialized = true;
Preferences::AddIntVarCache(&gPanRepaintInterval, "gfx.azpc.pan_repaint_interval", gPanRepaintInterval);
Preferences::AddIntVarCache(&gFlingRepaintInterval, "gfx.azpc.fling_repaint_interval", gFlingRepaintInterval);
Preferences::AddFloatVarCache(&gMinSkateSpeed, "gfx.azpc.min_skate_speed", gMinSkateSpeed);
Preferences::AddIntVarCache(&gTouchListenerTimeout, "gfx.azpc.touch_listener_timeout", gTouchListenerTimeout);
Preferences::AddIntVarCache(&gNumPaintDurationSamples, "gfx.azpc.num_paint_duration_samples", gNumPaintDurationSamples);
Preferences::AddFloatVarCache(&gTouchStartTolerance, "gfx.azpc.touch_start_tolerance", gTouchStartTolerance);
Preferences::AddFloatVarCache(&gXSkateSizeMultiplier, "gfx.azpc.x_skate_size_multiplier", gXSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gYSkateSizeMultiplier, "gfx.azpc.y_skate_size_multiplier", gYSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gXStationarySizeMultiplier, "gfx.azpc.x_stationary_size_multiplier", gXStationarySizeMultiplier);
Preferences::AddFloatVarCache(&gYStationarySizeMultiplier, "gfx.azpc.y_stationary_size_multiplier", gYStationarySizeMultiplier);
Preferences::AddIntVarCache(&gAsyncScrollThrottleTime, "apzc.asyncscroll.throttle", gAsyncScrollThrottleTime);
Preferences::AddIntVarCache(&gAsyncScrollTimeout, "apzc.asyncscroll.timeout", gAsyncScrollTimeout);
Preferences::AddBoolVarCache(&gAsyncZoomDisabled, "apzc.asynczoom.disabled", gAsyncZoomDisabled);
Preferences::AddBoolVarCache(&gCrossSlideEnabled, "apzc.cross_slide.enabled", gCrossSlideEnabled);
Preferences::AddIntVarCache(&gAxisLockMode, "apzc.axis_lock_mode", gAxisLockMode);
Preferences::AddIntVarCache(&gPanRepaintInterval, "apz.pan_repaint_interval", gPanRepaintInterval);
Preferences::AddIntVarCache(&gFlingRepaintInterval, "apz.fling_repaint_interval", gFlingRepaintInterval);
Preferences::AddFloatVarCache(&gMinSkateSpeed, "apz.min_skate_speed", gMinSkateSpeed);
Preferences::AddIntVarCache(&gTouchListenerTimeout, "apz.touch_listener_timeout", gTouchListenerTimeout);
Preferences::AddIntVarCache(&gNumPaintDurationSamples, "apz.num_paint_duration_samples", gNumPaintDurationSamples);
Preferences::AddFloatVarCache(&gTouchStartTolerance, "apz.touch_start_tolerance", gTouchStartTolerance);
Preferences::AddFloatVarCache(&gXSkateSizeMultiplier, "apz.x_skate_size_multiplier", gXSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gYSkateSizeMultiplier, "apz.y_skate_size_multiplier", gYSkateSizeMultiplier);
Preferences::AddFloatVarCache(&gXStationarySizeMultiplier, "apz.x_stationary_size_multiplier", gXStationarySizeMultiplier);
Preferences::AddFloatVarCache(&gYStationarySizeMultiplier, "apz.y_stationary_size_multiplier", gYStationarySizeMultiplier);
Preferences::AddIntVarCache(&gAsyncScrollThrottleTime, "apz.asyncscroll.throttle", gAsyncScrollThrottleTime);
Preferences::AddIntVarCache(&gAsyncScrollTimeout, "apz.asyncscroll.timeout", gAsyncScrollTimeout);
Preferences::AddBoolVarCache(&gAsyncZoomDisabled, "apz.asynczoom.disabled", gAsyncZoomDisabled);
Preferences::AddBoolVarCache(&gCrossSlideEnabled, "apz.cross_slide.enabled", gCrossSlideEnabled);
Preferences::AddIntVarCache(&gAxisLockMode, "apz.axis_lock_mode", gAxisLockMode);
gComputedTimingFunction = new ComputedTimingFunction();
gComputedTimingFunction->Init(

View File

@ -67,12 +67,12 @@ static int gMaxVelocityQueueSize = 5;
static void ReadAxisPrefs()
{
Preferences::AddFloatVarCache(&gMaxEventAcceleration, "gfx.axis.max_event_acceleration", gMaxEventAcceleration);
Preferences::AddFloatVarCache(&gFlingFriction, "gfx.axis.fling_friction", gFlingFriction);
Preferences::AddFloatVarCache(&gVelocityThreshold, "gfx.axis.velocity_threshold", gVelocityThreshold);
Preferences::AddFloatVarCache(&gAccelerationMultiplier, "gfx.axis.acceleration_multiplier", gAccelerationMultiplier);
Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "gfx.axis.fling_stopped_threshold", gFlingStoppedThreshold);
Preferences::AddIntVarCache(&gMaxVelocityQueueSize, "gfx.axis.max_velocity_queue_size", gMaxVelocityQueueSize);
Preferences::AddFloatVarCache(&gMaxEventAcceleration, "apz.max_event_acceleration", gMaxEventAcceleration);
Preferences::AddFloatVarCache(&gFlingFriction, "apz.fling_friction", gFlingFriction);
Preferences::AddFloatVarCache(&gVelocityThreshold, "apz.velocity_threshold", gVelocityThreshold);
Preferences::AddFloatVarCache(&gAccelerationMultiplier, "apz.acceleration_multiplier", gAccelerationMultiplier);
Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "apz.fling_stopped_threshold", gFlingStoppedThreshold);
Preferences::AddIntVarCache(&gMaxVelocityQueueSize, "apz.max_velocity_queue_size", gMaxVelocityQueueSize);
}
class ReadAxisPref MOZ_FINAL : public nsRunnable {

View File

@ -14,6 +14,7 @@ MOZ_THUMB_INTERWORK=toolchain-default
MOZ_FPU=toolchain-default
MOZ_FLOAT_ABI=toolchain-default
MOZ_SOFT_FLOAT=toolchain-default
MOZ_ALIGN=toolchain-default
MOZ_ARG_WITH_STRING(arch,
[ --with-arch=[[type|toolchain-default]]
@ -32,6 +33,7 @@ if test -z "$MOZ_ARCH"; then
MOZ_ARCH=armv7-a
MOZ_FPU=vfp
MOZ_FLOAT_ABI=softfp
MOZ_ALIGN=no
;;
arm-Darwin)
MOZ_ARCH=toolchain-default
@ -159,8 +161,28 @@ no)
;;
esac
case "$MOZ_ALIGN" in
no)
align_flag="-mno-unaligned-access"
;;
yes)
align_flag="-munaligned-access"
;;
*)
align_flag=""
;;
esac
if test -n "$align_flag"; then
_SAVE_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $align_flag"
AC_MSG_CHECKING(whether alignment flag ($align_flag) is supported)
AC_TRY_COMPILE([],[],,align_flag="")
CFLAGS="$_SAVE_CFLAGS"
fi
dnl Use echo to avoid accumulating space characters
all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag`
all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag $align_flag`
if test -n "$all_flags"; then
_SAVE_CFLAGS="$CFLAGS"
CFLAGS="$all_flags"

View File

@ -13,8 +13,6 @@ public class testBookmark extends AboutHomeTest {
private static String BOOKMARK_URL;
private static int WAIT_FOR_BOOKMARKED_TIMEOUT = 10000;
Navigation nav;
@Override
protected int getTestType() {
return TEST_MOCHITEST;
@ -22,7 +20,6 @@ public class testBookmark extends AboutHomeTest {
public void testBookmark() {
BOOKMARK_URL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
nav = new Navigation(mDevice);
runAboutHomeTest();
runMenuTest();
}
@ -70,7 +67,7 @@ public class testBookmark extends AboutHomeTest {
// Bookmark a page for the test
loadUrl(BOOKMARK_URL);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
nav.bookmark();
toggleBookmark();
mAsserter.is(waitForText(StringHelper.BOOKMARK_ADDED_LABEL), true, "bookmark added successfully");
}
@ -78,7 +75,7 @@ public class testBookmark extends AboutHomeTest {
// Go back to the page we bookmarked
loadUrl(BOOKMARK_URL);
waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
nav.bookmark();
toggleBookmark();
mAsserter.is(waitForText(StringHelper.BOOKMARK_REMOVED_LABEL), true, "bookmark removed successfully");
}
}

View File

@ -140,7 +140,7 @@ function onInstall(aEvent) {
return;
addApplication(aEvent.application);
document.getElementById("noapps").className = "hidden";
document.getElementById("main-container").classList.remove("hidden");
}
function onUninstall(aEvent) {
@ -149,6 +149,6 @@ function onUninstall(aEvent) {
let parent = node.parentNode;
parent.removeChild(node);
if (!parent.firstChild)
document.getElementById("noapps").className = "";
document.getElementById("main-container").classList.add("hidden");
}
}

View File

@ -36,17 +36,22 @@
<div class="header">
<div>&aboutApps.header;</div>
<div id="header-button" role="button" aria-label="&aboutApps.noApps.middle3;" pref="app.marketplaceURL" onclick="openLink(this);"/>
<div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL" onclick="openLink(this);"/>
</div>
<div id="main-container" class="list">
<div id="noapps" class="hidden">
&aboutApps.noApps.pre;<a id="marketplaceURL" pref="app.marketplaceURL">&aboutApps.noApps.middle3;</a>&aboutApps.noApps.post;
</div>
<div id="main-container" class="list hidden">
<div>
<div class="spacer" id="spacer1"> </div>
<div id="appgrid"/>
<div class="spacer" id="spacer1"> </div>
</div>
</div>
<div class="list-item" role="button" pref="app.marketplaceURL" onclick="openLink(this);">
<img class="icon" src="chrome://browser/skin/images/marketplace-logo.png" />
<div class="inner">
<div id="browse-title" class="title">&aboutApps.browseMarketplace;</div>
</div>
</div>
</body>
</html>

View File

@ -18,6 +18,7 @@ Cu.import("resource://gre/modules/JNI.jsm");
Cu.import('resource://gre/modules/Payment.jsm');
Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
Cu.import("resource://gre/modules/ContactService.jsm");
Cu.import("resource://gre/modules/NotificationDB.jsm");
Cu.import("resource://gre/modules/SpatialNavigation.jsm");
#ifdef ACCESSIBILITY

View File

@ -282,6 +282,8 @@
@BINPATH@/components/ContactManager.manifest
@BINPATH@/components/PhoneNumberService.js
@BINPATH@/components/PhoneNumberService.manifest
@BINPATH@/components/NotificationStorage.js
@BINPATH@/components/NotificationStorage.manifest
@BINPATH@/components/SettingsManager.js
@BINPATH@/components/SettingsManager.manifest
@BINPATH@/components/SettingsService.js
@ -385,8 +387,6 @@
@BINPATH@/components/Webapps.manifest
@BINPATH@/components/AppsService.js
@BINPATH@/components/AppsService.manifest
@BINPATH@/components/AppProtocolHandler.js
@BINPATH@/components/AppProtocolHandler.manifest
@BINPATH@/components/Push.js
@BINPATH@/components/Push.manifest

View File

@ -5,11 +5,6 @@
<!ENTITY aboutApps.title2 "Apps">
<!ENTITY aboutApps.header "Your Apps">
<!-- LOCALIZATION NOTE (aboutApps.noApps.pre): include a trailing space as needed -->
<!-- LOCALIZATION NOTE (aboutApps.noApps.middle): avoid leading/trailing spaces, this text is a link -->
<!-- LOCALIZATION NOTE (aboutApps.noApps.post): include a starting space as needed -->
<!ENTITY aboutApps.noApps.pre "No web apps installed. Get some from the ">
<!ENTITY aboutApps.noApps.middle3 "Firefox Marketplace">
<!ENTITY aboutApps.noApps.post ".">
<!ENTITY aboutApps.browseMarketplace "Browse the Firefox Marketplace">
<!ENTITY aboutApps.uninstall "Uninstall">
<!ENTITY aboutApps.addToHomescreen "Add to Home Screen">

View File

@ -2,15 +2,6 @@
* 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/. */
.inner {
background-color: #eef2f5;
min-height: 3.8em;
padding: 0.5em;
/* make room for the favicon */
-moz-margin-start: 4.5em;
}
.details {
width: 100%;
}
@ -33,14 +24,6 @@
bottom: -3px;
}
#browse-title {
margin-top: 1em;
background-image: url("chrome://browser/skin/images/chevron.png");
background-size: 8px 20px;
background-position: right;
background-repeat: no-repeat;
}
#header-button {
background-image: url("chrome://browser/skin/images/amo-logo.png"), url("chrome://browser/skin/images/chevron.png");
background-size: 20px 20px, 8px 20px;

View File

@ -7,16 +7,20 @@
}
#header-button {
background-image: url("chrome://browser/skin/images/marketplace-logo.png");
background-image: url("chrome://browser/skin/images/marketplace-logo.png"), url("chrome://browser/skin/images/chevron.png");
background-size: 32px 32px, 8px 20px;
background-position: left, right 0.5em center;
-moz-padding-start: 2.5em;
}
#main-container {
margin: 1em;
padding: 1em;
border-radius: 10px;
border: 1px solid grey;
background-color: white;
width: calc(100% - 4em);
padding: 2em;
background-color: #EEF2F5;
border-bottom: 1px solid #BAC2AC;
}
.hidden {
display: none;
}
.spacer {
@ -44,12 +48,3 @@
.app div {
pointer-events: none;
}
#noapps {
padding: 1em;
text-align: center;
}
.hidden {
display: none;
}

View File

@ -65,6 +65,15 @@ body {
background-image: none;
}
.inner {
background-color: #eef2f5;
min-height: 3.8em;
padding: 0.5em;
/* make room for the favicon */
-moz-margin-start: 4.5em;
}
/* Icons */
body[dir="ltr"] .icon {
left: 1.35em;
@ -132,3 +141,11 @@ body[dir="ltr"] .icon {
overflow: hidden;
flex: 1;
}
#browse-title {
margin-top: 1em;
background-image: url("chrome://browser/skin/images/chevron.png");
background-size: 8px 20px;
background-position: right;
background-repeat: no-repeat;
}

View File

@ -11,7 +11,7 @@
<Url type="text/html" method="GET" template="http://search.yahoo.com/search">
<Param name="p" value="{searchTerms}" />
<Param name="ei" value="UTF-8" />
<Param name="fr" value="mozilla_mobile_search" />
<MozParam name="fr" condition="top2" trueValue="mozilla_mobile_search" falseValue="" />
</Url>
<SearchForm>http://search.yahoo.com/</SearchForm>
</SearchPlugin>

View File

@ -287,7 +287,7 @@ pref("media.audio_data.enabled", true);
// 0 = FREE (No locking at all)
// 1 = STANDARD (Once locked, remain locked until scrolling ends)
// 2 = STICKY (Allow lock to be broken, with hysteresis)
pref("apzc.axis_lock_mode", 0);
pref("apz.axis_lock_mode", 0);
#ifdef XP_MACOSX
// Whether to run in native HiDPI mode on machines with "Retina"/HiDPI display;

View File

@ -69,6 +69,7 @@ LOCAL_INCLUDES = \
-I$(srcdir)/../mime \
-I$(srcdir)/../cache \
-I$(srcdir)/../protocol/about \
-I$(srcdir)/../protocol/app \
-I../dns \
$(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \
-I$(srcdir)/../protocol/$(d)) \

View File

@ -602,6 +602,18 @@
{0x93, 0x44, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \
}
/******************************************************************************
* netwerk/protocol/app/ classes
*/
#define NS_APPPROTOCOLHANDLER_CID \
{ /* {B6ED3030-9999-11d3-A178-0050041CAF44} */ \
0xb6ed3030, \
0x9999, \
0x11d3, \
{0xa1, 0x78, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44} \
}
/******************************************************************************
* netwerk/protocol/data/ classes
*/

View File

@ -243,6 +243,7 @@ namespace net {
NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
}
}
#include "AppProtocolHandler.h"
#ifdef NECKO_PROTOCOL_res
// resource
@ -772,6 +773,7 @@ NS_DEFINE_NAMED_CID(NS_CACHESERVICE_CID);
NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHESERVICE_CID);
NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHENAMESPACE_CID);
NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHE_CID);
NS_DEFINE_NAMED_CID(NS_APPPROTOCOLHANDLER_CID);
#ifdef NECKO_COOKIES
NS_DEFINE_NAMED_CID(NS_COOKIEMANAGER_CID);
NS_DEFINE_NAMED_CID(NS_COOKIESERVICE_CID);
@ -910,6 +912,7 @@ static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
{ &kNS_APPLICATIONCACHESERVICE_CID, false, nullptr, nsApplicationCacheServiceConstructor },
{ &kNS_APPLICATIONCACHENAMESPACE_CID, false, nullptr, nsApplicationCacheNamespaceConstructor },
{ &kNS_APPLICATIONCACHE_CID, false, nullptr, nsApplicationCacheConstructor },
{ &kNS_APPPROTOCOLHANDLER_CID, false, nullptr, AppProtocolHandler::Create },
#ifdef NECKO_COOKIES
{ &kNS_COOKIEMANAGER_CID, false, nullptr, nsICookieServiceConstructor },
{ &kNS_COOKIESERVICE_CID, false, nullptr, nsICookieServiceConstructor },
@ -1055,6 +1058,7 @@ static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
{ NS_APPLICATIONCACHESERVICE_CONTRACTID, &kNS_APPLICATIONCACHESERVICE_CID },
{ NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &kNS_APPLICATIONCACHENAMESPACE_CID },
{ NS_APPLICATIONCACHE_CONTRACTID, &kNS_APPLICATIONCACHE_CID },
{ NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "app", &kNS_APPPROTOCOLHANDLER_CID },
#ifdef NECKO_COOKIES
{ NS_COOKIEMANAGER_CONTRACTID, &kNS_COOKIEMANAGER_CID },
{ NS_COOKIESERVICE_CONTRACTID, &kNS_COOKIESERVICE_CID },

View File

@ -0,0 +1,424 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
/* 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/. */
#include "AppProtocolHandler.h"
#include "nsBaseChannel.h"
#include "nsJARChannel.h"
#include "nsNetCID.h"
#include "nsIAppsService.h"
#include "nsCxPusher.h"
#include "nsXULAppAPI.h"
/**
* This dummy channel implementation only provides enough functionality
* to return a fake 404 error when the caller asks for an app:// URL
* containing an unknown appId.
*/
class DummyChannel : public nsIJARChannel
, nsRunnable
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUEST
NS_DECL_NSICHANNEL
NS_DECL_NSIJARCHANNEL
DummyChannel();
NS_IMETHODIMP Run();
private:
bool mPending;
uint32_t mSuspendCount;
nsCOMPtr<nsISupports> mListenerContext;
nsCOMPtr<nsIStreamListener> mListener;
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsLoadFlags mLoadFlags;
};
NS_IMPL_ISUPPORTS3(DummyChannel, nsIRequest, nsIChannel, nsIJARChannel)
DummyChannel::DummyChannel() : mPending(false)
, mSuspendCount(0)
, mLoadFlags(LOAD_NORMAL)
{
}
NS_IMETHODIMP DummyChannel::GetName(nsACString &result)
{
result = "dummy_app_channel";
return NS_OK;
}
NS_IMETHODIMP DummyChannel::GetStatus(nsresult *aStatus)
{
*aStatus = NS_ERROR_FILE_NOT_FOUND;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::IsPending(bool *aResult)
{
*aResult = mPending;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Suspend()
{
mSuspendCount++;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Resume()
{
if (mSuspendCount <= 0) {
return NS_ERROR_UNEXPECTED;
}
if (--mSuspendCount == 0) {
NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Open(nsIInputStream**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext)
{
mListener = aListener;
mListenerContext = aContext;
mPending = true;
if (mLoadGroup) {
mLoadGroup->AddRequest(this, aContext);
}
if (mSuspendCount == 0) {
NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
}
return NS_OK;
}
// nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
// a 404 error.
NS_IMETHODIMP DummyChannel::GetIsUnsafe(bool *aResult)
{
*aResult = false;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::SetAppURI(nsIURI *aURI)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::Run()
{
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
NS_ENSURE_SUCCESS(rv, rv);
mPending = false;
rv = mListener->OnStopRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
NS_ENSURE_SUCCESS(rv, rv);
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
}
mListener = nullptr;
mListenerContext = nullptr;
rv = SetNotificationCallbacks(nullptr);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP DummyChannel::Cancel(nsresult)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
{
*aLoadGroup = mLoadGroup;
NS_IF_ADDREF(*aLoadGroup);
return NS_OK;
}
NS_IMETHODIMP DummyChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
{
mLoadGroup = aLoadGroup;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
{
*aLoadFlags = mLoadFlags;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
{
mLoadFlags = aLoadFlags;
return NS_OK;
}
NS_IMETHODIMP DummyChannel::GetOriginalURI(nsIURI**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetOriginalURI(nsIURI*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetOwner(nsISupports**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetOwner(nsISupports*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetNotificationCallbacks(nsIInterfaceRequestor**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetNotificationCallbacks(nsIInterfaceRequestor*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetSecurityInfo(nsISupports**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentType(nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentType(const nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentCharset(nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentCharset(const nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentLength(int64_t*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentLength(int64_t)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentDisposition(uint32_t*)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentDisposition(uint32_t)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetURI(nsIURI**)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentDispositionFilename(nsAString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::SetContentDispositionFilename(nsAString const &)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP DummyChannel::GetContentDispositionHeader(nsACString&)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
/**
* app:// protocol implementation.
*/
AppProtocolHandler::AppProtocolHandler() {
}
AppProtocolHandler::~AppProtocolHandler() {
mAppInfoCache.Clear();
}
NS_IMPL_ISUPPORTS1(AppProtocolHandler, nsIProtocolHandler)
/* static */
nsresult
AppProtocolHandler::Create(nsISupports* aOuter,
const nsIID& aIID,
void* *aResult)
{
AppProtocolHandler* ph = new AppProtocolHandler();
if (ph == nullptr) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(ph);
nsresult rv = ph->QueryInterface(aIID, aResult);
NS_RELEASE(ph);
return rv;
}
NS_IMETHODIMP
AppProtocolHandler::GetScheme(nsACString &aResult)
{
aResult.AssignLiteral("app");
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::GetDefaultPort(int32_t *aResult)
{
// No ports for the app protocol.
*aResult = -1;
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::GetProtocolFlags(uint32_t *aResult)
{
*aResult = URI_NOAUTH |
URI_DANGEROUS_TO_LOAD |
URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM;
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::NewURI(const nsACString &aSpec,
const char *aCharset, // ignore charset info
nsIURI *aBaseURI,
nsIURI **result)
{
nsresult rv;
nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
NS_ENSURE_SUCCESS(rv, rv);
rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
NS_ENSURE_SUCCESS(rv, rv);
surl->SetMutable(false);
NS_ADDREF(*result = url);
return NS_OK;
}
// We map app://ABCDEF/path/to/file.ext to
// jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
NS_IMETHODIMP
AppProtocolHandler::NewChannel(nsIURI* aUri, nsIChannel* *aResult)
{
NS_ENSURE_ARG_POINTER(aUri);
nsJARChannel* channel = new nsJARChannel();
if (!channel) {
return NS_ERROR_OUT_OF_MEMORY;
}
nsAutoCString host;
nsresult rv = aUri->GetHost(host);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString fileSpec;
nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
rv = url->GetFilePath(fileSpec);
NS_ENSURE_SUCCESS(rv, rv);
mozilla::dom::AppInfo appInfo;
if (!mAppInfoCache.Get(host, &appInfo)) {
nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
if (!appsService) {
return NS_ERROR_FAILURE;
}
JS::Value jsInfo;
rv = appsService->GetAppInfo(NS_ConvertUTF8toUTF16(host), &jsInfo);
if (NS_FAILED(rv)) {
// Return a DummyChannel.
delete channel;
NS_IF_ADDREF(*aResult = new DummyChannel());
return NS_OK;
}
mozilla::AutoSafeJSContext cx;
if (!appInfo.Init(cx, JS::Handle<JS::Value>::fromMarkedLocation(&jsInfo)) ||
appInfo.mPath.IsEmpty()) {
printf_stderr("!! No appInfo for %s\n", host.get());
// Return a DummyChannel.
delete channel;
NS_IF_ADDREF(*aResult = new DummyChannel());
return NS_OK;
}
mAppInfoCache.Put(host, appInfo);
}
bool noRemote = (appInfo.mIsCoreApp ||
XRE_GetProcessType() == GeckoProcessType_Default);
// In-parent and CoreApps can directly access files, so use jar:file://
nsAutoCString jarSpec(noRemote ? "jar:file://"
: "jar:remoteopenfile://");
jarSpec += NS_ConvertUTF16toUTF8(appInfo.mPath) +
NS_LITERAL_CSTRING("/application.zip!") +
fileSpec;
nsCOMPtr<nsIURI> jarURI;
rv = NS_NewURI(getter_AddRefs(jarURI),
jarSpec, nullptr, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->Init(jarURI);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->SetAppURI(aUri);
NS_ENSURE_SUCCESS(rv, rv);
rv = channel->SetOriginalURI(aUri);
NS_ENSURE_SUCCESS(rv, rv);
NS_ADDREF(*aResult = channel);
return NS_OK;
}
NS_IMETHODIMP
AppProtocolHandler::AllowPort(int32_t aPort, const char *aScheme, bool *aRetval)
{
// No port allowed for this scheme.
*aRetval = false;
return NS_OK;
}

View File

@ -0,0 +1,35 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
/* 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/. */
#ifndef AppProtocolHandler_
#define AppProtocolHandler_
#include "nsIProtocolHandler.h"
#include "nsDataHashtable.h"
#include "mozilla/dom/AppInfoBinding.h"
class AppProtocolHandler : public nsIProtocolHandler
{
public:
NS_DECL_ISUPPORTS
// nsIProtocolHandler methods:
NS_DECL_NSIPROTOCOLHANDLER
// AppProtocolHandler methods:
AppProtocolHandler();
virtual ~AppProtocolHandler();
// Define a Create method to be used with a factory:
static nsresult Create(nsISupports* aOuter,
const nsIID& aIID,
void* *aResult);
private:
nsDataHashtable<nsCStringHashKey, mozilla::dom::AppInfo> mAppInfoCache;
};
#endif /* AppProtocolHandler_ */

Some files were not shown because too many files have changed in this diff Show More