Merge mozilla-central to UX

This commit is contained in:
Matthew Noorenberghe 2013-11-05 01:04:20 -08:00
commit e8d8f80693
211 changed files with 3937 additions and 2711 deletions

View File

@ -259,7 +259,7 @@ endif
ifdef MOZ_PSEUDO_DERECURSE
# Interdependencies for parallel export.
js/xpconnect/src/export: dom/bindings/export
js/xpconnect/src/export: dom/bindings/export xpcom/xpidl/export
accessible/src/xpcom/export: xpcom/xpidl/export
js/src/export: mfbt/export
ifdef ENABLE_CLANG_PLUGIN

View File

@ -18,8 +18,7 @@ endif
xpcAccEvents.cpp: $(srcdir)/AccEvents.conf \
$(srcdir)/AccEventGen.py \
$(LIBXUL_DIST)/sdk/bin/header.py \
$(LIBXUL_DIST)/sdk/bin/xpidl.py \
$(DEPTH)/js/src/js-confdefs.h
$(LIBXUL_DIST)/sdk/bin/xpidl.py
$(PYTHON) $(topsrcdir)/config/pythonpath.py \
-I$(LIBXUL_DIST)/sdk/bin \
$(srcdir)/AccEventGen.py \

View File

@ -1,4 +1,4 @@
{
"revision": "00ceae21c52602059b7614b661bc39a3c73c84de",
"revision": "e9d3946c6e4c26c60f67b8efac40e14785b634d3",
"repo_path": "/integration/gaia-central"
}

View File

@ -351,18 +351,8 @@ pref("browser.download.debug", false);
pref("browser.download.saveLinkAsFilenameTimeout", 4000);
pref("browser.download.useDownloadDir", true);
pref("browser.download.folderList", 1);
pref("browser.download.manager.showAlertOnComplete", true);
pref("browser.download.manager.showAlertInterval", 2000);
pref("browser.download.manager.retention", 2);
pref("browser.download.manager.showWhenStarting", true);
pref("browser.download.manager.closeWhenDone", false);
pref("browser.download.manager.focusWhenStarting", false);
pref("browser.download.manager.flashCount", 2);
pref("browser.download.manager.addToRecentDocs", true);
pref("browser.download.manager.quitBehavior", 0);
pref("browser.download.manager.scanWhenDone", true);
pref("browser.download.manager.resumeOnWakeDelay", 10000);
// This allows disabling the animated notifications shown by
@ -372,10 +362,6 @@ pref("browser.download.animateNotifications", true);
// This records whether or not the panel has been shown at least once.
pref("browser.download.panel.shown", false);
// This records whether or not at least one session with the Downloads Panel
// enabled has been completed already.
pref("browser.download.panel.firstSessionCompleted", false);
#ifndef XP_MACOSX
pref("browser.helperApps.deleteTempFileOnExit", true);
#endif
@ -587,7 +573,11 @@ pref("browser.gesture.twist.left", "cmd_gestureRotateLeft");
pref("browser.gesture.twist.end", "cmd_gestureRotateEnd");
pref("browser.gesture.tap", "cmd_fullZoomReset");
#ifndef RELEASE_BUILD
pref("browser.snapshots.limit", 5);
#else
pref("browser.snapshots.limit", 0);
#endif
// 0: Nothing happens
// 1: Scrolling contents
@ -1018,10 +1008,6 @@ pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
// source, and this would propagate automatically to other,
// uncompromised Sync-connected devices.
pref("services.sync.prefs.sync.app.update.mode", true);
pref("services.sync.prefs.sync.browser.download.manager.closeWhenDone", true);
pref("services.sync.prefs.sync.browser.download.manager.retention", true);
pref("services.sync.prefs.sync.browser.download.manager.scanWhenDone", true);
pref("services.sync.prefs.sync.browser.download.manager.showWhenStarting", true);
pref("services.sync.prefs.sync.browser.formfill.enable", true);
pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
pref("services.sync.prefs.sync.browser.offline-apps.notify", true);

View File

@ -7,8 +7,6 @@ include $(topsrcdir)/config/config.mk
abs_srcdir = $(abspath $(srcdir))
CHROME_DEPS += $(abs_srcdir)/content/overrides/app-license.html
include $(topsrcdir)/config/rules.mk
PRE_RELEASE_SUFFIX := ""

View File

@ -72,12 +72,15 @@ var gPluginHandler = {
if (aName == "Shockwave Flash")
return "Adobe Flash";
// Clean up the plugin name by stripping off any trailing version numbers
// or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
// Clean up the plugin name by stripping off parenthetical clauses,
// trailing version numbers or "plugin".
// EG, "Foo Bar (Linux) Plugin 1.23_02" --> "Foo Bar"
// Do this by first stripping the numbers, etc. off the end, and then
// removing "Plugin" (and then trimming to get rid of any whitespace).
// (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
let newName = aName.replace(/\(.*?\)/g, "").
replace(/[\s\d\.\-\_\(\)]+$/, "").
replace(/\bplug-?in\b/i, "").trim();
return newName;
},

View File

@ -1078,24 +1078,13 @@ var gBrowserInit = {
// auto-resume downloads begin (such as after crashing or quitting with
// active downloads) and speeds up the first-load of the download manager UI.
// If the user manually opens the download manager before the timeout, the
// downloads will start right away, and getting the service again won't hurt.
// downloads will start right away, and initializing again won't hurt.
setTimeout(function() {
try {
let DownloadsCommon =
Cu.import("resource:///modules/DownloadsCommon.jsm", {}).DownloadsCommon;
if (DownloadsCommon.useJSTransfer) {
// Open the data link without initalizing nsIDownloadManager.
DownloadsCommon.initializeAllDataLinks();
let DownloadsTaskbar =
Cu.import("resource:///modules/DownloadsTaskbar.jsm", {}).DownloadsTaskbar;
DownloadsTaskbar.registerIndicator(window);
} else {
// Initalizing nsIDownloadManager will trigger the data link.
Services.downloads;
let DownloadTaskbarProgress =
Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
DownloadTaskbarProgress.onBrowserWindowLoad(window);
}
Cu.import("resource:///modules/DownloadsCommon.jsm", {})
.DownloadsCommon.initializeAllDataLinks();
Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
.DownloadsTaskbar.registerIndicator(window);
} catch (ex) {
Cu.reportError(ex);
}
@ -3156,10 +3145,22 @@ function OpenBrowserWindow(options)
doc != document &&
doc.defaultView == win) {
Services.obs.removeObserver(newDocumentShown, "document-shown");
Services.obs.removeObserver(windowClosed, "domwindowclosed");
TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
}
};
}
function windowClosed(subject) {
if (subject == win) {
Services.obs.removeObserver(newDocumentShown, "document-shown");
Services.obs.removeObserver(windowClosed, "domwindowclosed");
}
}
// Make sure to remove the 'document-shown' observer in case the window
// is being closed right after it was opened to avoid leaking.
Services.obs.addObserver(newDocumentShown, "document-shown", false);
Services.obs.addObserver(windowClosed, "domwindowclosed", false);
var charsetArg = new String();
var handler = Components.classes["@mozilla.org/browser/clh;1"]

View File

@ -150,7 +150,6 @@
footertype="promobox"
orient="vertical"
ignorekeys="true"
consumeoutsideclicks="true"
hidden="true"
onpopupshown="StarUI.panelShown(event);"
aria-labelledby="editBookmarkPanelTitle">
@ -199,7 +198,6 @@
<panel id="UITourTooltip"
type="arrow"
hidden="true"
consumeoutsideclicks="false"
noautofocus="true"
align="start"
orient="vertical"
@ -214,7 +212,6 @@
<panel id="socialActivatedNotification"
type="arrow"
hidden="true"
consumeoutsideclicks="true"
align="start"
orient="horizontal"
role="alert">
@ -245,7 +242,6 @@
orient="horizontal"
onpopupshowing="SocialShare.onShowing()"
onpopuphidden="SocialShare.onHidden()"
consumeoutsideclicks="true"
hidden="true">
<vbox class="social-share-toolbar">
<vbox id="social-share-provider-buttons" flex="1"/>
@ -266,7 +262,6 @@
hidden="true"
flip="slide"
rolluponmousewheel="true"
consumeoutsideclicks="false"
noautofocus="true"
position="topcenter topright"/>
@ -320,7 +315,6 @@
type="arrow"
hidden="true"
noautofocus="true"
consumeoutsideclicks="true"
onpopupshown="if (event.target == this)
gIdentityHandler.onPopupShown(event);"
orient="vertical"

View File

@ -316,48 +316,25 @@ Sanitizer.prototype = {
downloads: {
clear: function ()
{
if (DownloadsCommon.useJSTransfer) {
Task.spawn(function () {
let filterByTime = null;
if (this.range) {
// Convert microseconds back to milliseconds for date comparisons.
let rangeBeginMs = this.range[0] / 1000;
let rangeEndMs = this.range[1] / 1000;
filterByTime = download => download.startTime >= rangeBeginMs &&
download.startTime <= rangeEndMs;
}
// Clear all completed/cancelled downloads
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(filterByTime);
}.bind(this)).then(null, Components.utils.reportError);
}
else {
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
Task.spawn(function () {
let filterByTime = null;
if (this.range) {
// First, remove the completed/cancelled downloads
dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
// Convert microseconds back to milliseconds for date comparisons.
let rangeBeginMs = this.range[0] / 1000;
let rangeEndMs = this.range[1] / 1000;
filterByTime = download => download.startTime >= rangeBeginMs &&
download.startTime <= rangeEndMs;
}
else {
// Clear all completed/cancelled downloads
dlMgr.cleanUp();
dlMgr.cleanUpPrivate();
}
}
// Clear all completed/cancelled downloads
let list = yield Downloads.getList(Downloads.ALL);
list.removeFinished(filterByTime);
}.bind(this)).then(null, Components.utils.reportError);
},
canClear : function(aCallback, aArg)
{
if (DownloadsCommon.useJSTransfer) {
aCallback("downloads", true, aArg);
}
else {
var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
.getService(Components.interfaces.nsIDownloadManager);
aCallback("downloads", dlMgr.canCleanUp || dlMgr.canCleanUpPrivate, aArg);
}
aCallback("downloads", true, aArg);
return false;
}
},

View File

@ -95,5 +95,6 @@ function part8() {
ok(objLoadingContent.activated, "plugin should be activated now");
gNewWindow.close();
gNewWindow = null;
finish();
}

View File

@ -42,7 +42,14 @@ function finishTest() {
gPermissionManager.remove("127.0.0.1:8888", gSecondTestPermissionString);
Services.prefs.clearUserPref("plugins.click_to_play");
gBrowser.removeCurrentTab();
finish();
gPageInfo = null;
gNextTest = null;
gTestBrowser = null;
gPluginHost = null;
gPermissionManager = null;
executeSoon(finish);
}
function test() {

View File

@ -1,13 +1,12 @@
function test() {
waitForExplicitFinish();
var privateWin = OpenBrowserWindow({private: true});
privateWin.addEventListener("load", function onload() {
privateWin.removeEventListener("load", onload, false);
ok(true, "Load listener called");
whenDelayedStartupFinished(privateWin, function () {
privateWin.BrowserOpenTab();
privateWin.BrowserTryToCloseWindow();
ok(true, "didn't prompt");
finish();
}, false);
}
executeSoon(finish);
});
}

View File

@ -134,14 +134,9 @@ const DownloadsPanel = {
window.addEventListener("unload", this.onWindowUnload, false);
// Ensure that the Download Manager service is running. This resumes
// active downloads if required. If there are downloads to be shown in the
// panel, starting the service will make us load their data asynchronously.
if (DownloadsCommon.useJSTransfer) {
DownloadsCommon.initializeAllDataLinks();
} else {
Services.downloads;
}
// Load and resume active downloads if required. If there are downloads to
// be shown in the panel, they will be loaded asynchronously.
DownloadsCommon.initializeAllDataLinks();
// Now that data loading has eventually started, load the required XUL
// elements and initialize our views.
@ -784,26 +779,6 @@ const DownloadsView = {
DownloadsPanel.onViewLoadCompleted();
},
/**
* Called when the downloads database becomes unavailable (for example,
* entering Private Browsing Mode). References to existing data should be
* discarded.
*/
onDataInvalidated: function DV_onDataInvalidated()
{
DownloadsCommon.log("Downloads data has been invalidated. Cleaning up",
"DownloadsView.");
DownloadsPanel.terminate();
// Clear the list by replacing with a shallow copy.
let emptyView = this.richListBox.cloneNode(false);
this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
this.richListBox = emptyView;
this._viewItems = {};
this._dataItems = [];
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
@ -1481,7 +1456,8 @@ DownloadsViewItemController.prototype = {
downloadsCmd_open: function DVIC_downloadsCmd_open()
{
this.dataItem.openLocalFile(window);
this.dataItem.openLocalFile();
// We explicitly close the panel here to give the user the feedback that
// their click has been received, and we're handling the action.
// Otherwise, we'd have to wait for the file-type handler to execute

View File

@ -46,7 +46,6 @@
type="arrow"
orient="vertical"
level="top"
consumeoutsideclicks="true"
onpopupshown="DownloadsPanel.onPopupShown(event);"
onpopuphidden="DownloadsPanel.onPopupHidden(event);">
<!-- The following popup menu should be a child of the panel element,

View File

@ -56,16 +56,11 @@ const DownloadsButton = {
* This function is called asynchronously just after window initialization.
*
* NOTE: This function should limit the input/output it performs to improve
* startup time, and in particular should not cause the Download Manager
* service to start.
* startup time.
*/
initializeIndicator: function DB_initializeIndicator()
{
if (!DownloadsCommon.useToolkitUI) {
DownloadsIndicatorView.ensureInitialized();
} else {
DownloadsIndicatorView.ensureTerminated();
}
DownloadsIndicatorView.ensureInitialized();
},
/**
@ -95,11 +90,7 @@ const DownloadsButton = {
customizeDone: function DB_customizeDone()
{
this._customizing = false;
if (!DownloadsCommon.useToolkitUI) {
DownloadsIndicatorView.afterCustomize();
} else {
DownloadsIndicatorView.ensureTerminated();
}
DownloadsIndicatorView.afterCustomize();
},
/**
@ -502,18 +493,12 @@ const DownloadsIndicatorView = {
onCommand: function DIV_onCommand(aEvent)
{
if (DownloadsCommon.useToolkitUI) {
// The panel won't suppress attention for us, we need to clear now.
DownloadsCommon.getIndicatorData(window).attention = false;
BrowserDownloadsUI();
// If the downloads button is in the menu panel, open the Library
let widgetGroup = CustomizableUI.getWidget("downloads-button");
if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
DownloadsPanel.showDownloadsHistory();
} else {
// If the downloads button is in the menu panel, open the Library
let widgetGroup = CustomizableUI.getWidget("downloads-button");
if (widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL) {
DownloadsPanel.showDownloadsHistory();
} else {
DownloadsPanel.showPanel();
}
DownloadsPanel.showPanel();
}
aEvent.stopPropagation();

View File

@ -63,8 +63,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
"resource:///modules/RecentWindow.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
@ -75,9 +73,6 @@ const nsIDM = Ci.nsIDownloadManager;
const kDownloadsStringBundleUrl =
"chrome://browser/locale/downloads/downloads.properties";
const kPrefBdmScanWhenDone = "browser.download.manager.scanWhenDone";
const kPrefBdmAlertOnExeOpen = "browser.download.manager.alertOnEXEOpen";
const kDownloadsStringsRequiringFormatting = {
sizeWithUnits: true,
shortTimeLeftSeconds: true,
@ -234,16 +229,6 @@ this.DownloadsCommon = {
return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
},
/**
* 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()
{
return false;
},
/**
* Indicates whether we should show visual notification on the indicator
* when a download event is triggered.
@ -269,39 +254,12 @@ this.DownloadsCommon = {
},
/**
* Initializes the data link for both the private and non-private downloads
* data objects.
*
* @param aDownloadManagerService
* Reference to the service implementing nsIDownloadManager. We need
* this because getService isn't available for us when this method is
* called, and we must ensure to register our listeners before the
* getService call for the Download Manager returns.
* Initializes the Downloads back-end and starts receiving events for both the
* private and non-private downloads data objects.
*/
initializeAllDataLinks: function DC_initializeAllDataLinks(aDownloadManagerService) {
DownloadsData.initializeDataLink(aDownloadManagerService);
PrivateDownloadsData.initializeDataLink(aDownloadManagerService);
},
/**
* Terminates the data link for both the private and non-private downloads
* data objects.
*/
terminateAllDataLinks: function DC_terminateAllDataLinks() {
DownloadsData.terminateDataLink();
PrivateDownloadsData.terminateDataLink();
},
/**
* Reloads the specified kind of downloads from the non-private store.
* This method must only be called when Private Browsing Mode is disabled.
*
* @param aActiveOnly
* True to load only active downloads from the database.
*/
ensureAllPersistentDataLoaded:
function DC_ensureAllPersistentDataLoaded(aActiveOnly) {
DownloadsData.ensurePersistentDataLoaded(aActiveOnly);
initializeAllDataLinks: function () {
DownloadsData.initializeDataLink();
PrivateDownloadsData.initializeDataLink();
},
/**
@ -566,15 +524,6 @@ XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
return parseFloat(sysInfo.getProperty("version")) >= 6;
});
/**
* 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 () {
return true;
});
////////////////////////////////////////////////////////////////////////////////
//// DownloadsData
@ -611,76 +560,36 @@ function DownloadsDataCtor(aPrivate) {
// data changes.
this._views = [];
if (DownloadsCommon.useJSTransfer) {
// Maps Download objects to DownloadDataItem objects.
this._downloadToDataItemMap = new Map();
}
// Maps Download objects to DownloadDataItem objects.
this._downloadToDataItemMap = new Map();
}
DownloadsDataCtor.prototype = {
/**
* Starts receiving events for current downloads.
*
* @param aDownloadManagerService
* Reference to the service implementing nsIDownloadManager. We need
* this because getService isn't available for us when this method is
* called, and we must ensure to register our listeners before the
* getService call for the Download Manager returns.
*/
initializeDataLink: function DD_initializeDataLink(aDownloadManagerService)
initializeDataLink: function ()
{
// Start receiving real-time events.
if (DownloadsCommon.useJSTransfer) {
if (!this._dataLinkInitialized) {
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
this._dataLinkInitialized = true;
}
} else {
aDownloadManagerService.addPrivacyAwareListener(this);
Services.obs.addObserver(this, "download-manager-remove-download-guid",
false);
if (!this._dataLinkInitialized) {
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
this._dataLinkInitialized = true;
}
},
_dataLinkInitialized: false,
/**
* Stops receiving events for current downloads and cancels any pending read.
*/
terminateDataLink: function DD_terminateDataLink()
{
if (DownloadsCommon.useJSTransfer) {
Cu.reportError("terminateDataLink not applicable with useJSTransfer");
return;
}
this._terminateDataAccess();
// Stop receiving real-time events.
Services.obs.removeObserver(this, "download-manager-remove-download-guid");
Services.downloads.removeListener(this);
},
/**
* True if there are finished downloads that can be removed from the list.
*/
get canRemoveFinished()
{
if (DownloadsCommon.useJSTransfer) {
for (let [, dataItem] of Iterator(this.dataItems)) {
if (dataItem && !dataItem.inProgress) {
return true;
}
}
return false;
} else {
if (this._isPrivate) {
return Services.downloads.canCleanUpPrivate;
} else {
return Services.downloads.canCleanUp;
for (let [, dataItem] of Iterator(this.dataItems)) {
if (dataItem && !dataItem.inProgress) {
return true;
}
}
return false;
},
/**
@ -688,18 +597,10 @@ DownloadsDataCtor.prototype = {
*/
removeFinished: function DD_removeFinished()
{
if (DownloadsCommon.useJSTransfer) {
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.removeFinished())
.then(null, Cu.reportError);
} else {
if (this._isPrivate) {
Services.downloads.cleanUpPrivate();
} else {
Services.downloads.cleanUp();
}
}
let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
: Downloads.PUBLIC);
promiseList.then(list => list.removeFinished())
.then(null, Cu.reportError);
},
//////////////////////////////////////////////////////////////////////////////
@ -753,7 +654,7 @@ DownloadsDataCtor.prototype = {
let wasInProgress = aDataItem.inProgress;
let wasDone = aDataItem.done;
aDataItem.updateFromJSDownload();
aDataItem.updateFromDownload();
if (wasInProgress && !aDataItem.inProgress) {
aDataItem.endTime = Date.now();
@ -835,394 +736,10 @@ DownloadsDataCtor.prototype = {
function (dataItem) aView.onDataItemAdded(dataItem, false)
);
// Notify the view that all data is available unless loading is in progress.
if (!this._pendingStatement) {
aView.onDataLoadCompleted();
}
// Notify the view that all data is available.
aView.onDataLoadCompleted();
},
//////////////////////////////////////////////////////////////////////////////
//// In-memory downloads data store
/**
* Clears the loaded data.
*/
clear: function DD_clear()
{
this._terminateDataAccess();
this.dataItems = {};
},
/**
* Returns the data item associated with the provided source object. The
* source can be a download object that we received from the Download Manager
* because of a real-time notification, or a row from the downloads database,
* during the asynchronous data load.
*
* In case we receive download status notifications while we are still
* populating the list of downloads from the database, we want the real-time
* status to take precedence over the state that is read from the database,
* which might be older. This is achieved by creating the download item if
* it's not already in the list, but never updating the returned object using
* the data from the database, if the object already exists.
*
* @param aSource
* Object containing the data with which the item should be initialized
* if it doesn't already exist in the list. This should implement
* either nsIDownload or mozIStorageRow. If the item exists, this
* argument is only used to retrieve the download identifier.
* @param aMayReuseGUID
* If false, indicates that the download should not be added if a
* download with the same identifier was removed in the meantime. This
* ensures that, while loading the list asynchronously, downloads that
* have been removed in the meantime do no reappear inadvertently.
*
* @return New or existing data item, or null if the item was deleted from the
* list of available downloads.
*/
_getOrAddDataItem: function DD_getOrAddDataItem(aSource, aMayReuseGUID)
{
let downloadGuid = (aSource instanceof Ci.nsIDownload)
? aSource.guid
: aSource.getResultByName("guid");
if (downloadGuid in this.dataItems) {
let existingItem = this.dataItems[downloadGuid];
if (existingItem || !aMayReuseGUID) {
// Returns null if the download was removed and we can't reuse the item.
return existingItem;
}
}
DownloadsCommon.log("Creating a new DownloadsDataItem with downloadGuid =",
downloadGuid);
let dataItem = new DownloadsDataItem(aSource);
this.dataItems[downloadGuid] = dataItem;
// Create the view items before returning.
let addToStartOfList = aSource instanceof Ci.nsIDownload;
this._views.forEach(
function (view) view.onDataItemAdded(dataItem, addToStartOfList)
);
return dataItem;
},
/**
* Removes the data item with the specified identifier.
*
* This method can be called at most once per download identifier.
*/
_removeDataItem: function DD_removeDataItem(aDownloadId)
{
if (aDownloadId in this.dataItems) {
let dataItem = this.dataItems[aDownloadId];
this.dataItems[aDownloadId] = null;
this._views.forEach(
function (view) view.onDataItemRemoved(dataItem)
);
}
},
//////////////////////////////////////////////////////////////////////////////
//// Persistent data loading
/**
* Represents an executing statement, allowing its cancellation.
*/
_pendingStatement: null,
/**
* Indicates which kind of items from the persistent downloads database have
* been fully loaded in memory and are available to the views. This can
* assume the value of one of the kLoad constants.
*/
_loadState: 0,
/** No downloads have been fully loaded yet. */
get kLoadNone() 0,
/** All the active downloads in the database are loaded in memory. */
get kLoadActive() 1,
/** All the downloads in the database are loaded in memory. */
get kLoadAll() 2,
/**
* Reloads the specified kind of downloads from the persistent database. This
* method must only be called when Private Browsing Mode is disabled.
*
* @param aActiveOnly
* True to load only active downloads from the database.
*/
ensurePersistentDataLoaded:
function DD_ensurePersistentDataLoaded(aActiveOnly)
{
if (this == PrivateDownloadsData) {
Cu.reportError("ensurePersistentDataLoaded should not be called on PrivateDownloadsData");
return;
}
if (this._pendingStatement) {
// We are already in the process of reloading all downloads.
return;
}
if (aActiveOnly) {
if (this._loadState == this.kLoadNone) {
DownloadsCommon.log("Loading only active downloads from the persistence database");
// Indicate to the views that a batch loading operation is in progress.
this._views.forEach(
function (view) view.onDataLoadStarting()
);
// Reload the list using the Download Manager service. The list is
// returned in no particular order.
let downloads = Services.downloads.activeDownloads;
while (downloads.hasMoreElements()) {
let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
this._getOrAddDataItem(download, true);
}
this._loadState = this.kLoadActive;
// Indicate to the views that the batch loading operation is complete.
this._views.forEach(
function (view) view.onDataLoadCompleted()
);
DownloadsCommon.log("Active downloads done loading.");
}
} else {
if (this._loadState != this.kLoadAll) {
// Load only the relevant columns from the downloads database. The
// columns are read in the _initFromDataRow method of DownloadsDataItem.
// Order by descending download identifier so that the most recent
// downloads are notified first to the listening views.
DownloadsCommon.log("Loading all downloads from the persistence database.");
let dbConnection = Services.downloads.DBConnection;
let statement = dbConnection.createAsyncStatement(
"SELECT guid, target, name, source, referrer, state, "
+ "startTime, endTime, currBytes, maxBytes "
+ "FROM moz_downloads "
+ "ORDER BY startTime DESC"
);
try {
this._pendingStatement = statement.executeAsync(this);
} finally {
statement.finalize();
}
}
}
},
/**
* Cancels any pending data access and ensures views are notified.
*/
_terminateDataAccess: function DD_terminateDataAccess()
{
if (this._pendingStatement) {
this._pendingStatement.cancel();
this._pendingStatement = null;
}
// Close all the views on the current data. Create a copy of the array
// because some views might unregister while processing this event.
Array.slice(this._views, 0).forEach(
function (view) view.onDataInvalidated()
);
},
//////////////////////////////////////////////////////////////////////////////
//// mozIStorageStatementCallback
handleResult: function DD_handleResult(aResultSet)
{
for (let row = aResultSet.getNextRow();
row;
row = aResultSet.getNextRow()) {
// Add the download to the list and initialize it with the data we read,
// unless we already received a notification providing more reliable
// information for this download.
this._getOrAddDataItem(row, false);
}
},
handleError: function DD_handleError(aError)
{
DownloadsCommon.error("Database statement execution error (",
aError.result, "): ", aError.message);
},
handleCompletion: function DD_handleCompletion(aReason)
{
DownloadsCommon.log("Loading all downloads from database completed with reason:",
aReason);
this._pendingStatement = null;
// To ensure that we don't inadvertently delete more downloads from the
// database than needed on shutdown, we should update the load state only if
// the operation completed successfully.
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
this._loadState = this.kLoadAll;
}
// Indicate to the views that the batch loading operation is complete, even
// if the lookup failed or was canceled. The only possible glitch happens
// in case the database backend changes while loading data, when the views
// would open and immediately close. This case is rare enough not to need a
// special treatment.
this._views.forEach(
function (view) view.onDataLoadCompleted()
);
},
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function DD_observe(aSubject, aTopic, aData)
{
switch (aTopic) {
case "download-manager-remove-download-guid":
// If a single download was removed, remove the corresponding data item.
if (aSubject) {
let downloadGuid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
DownloadsCommon.log("A single download with id",
downloadGuid, "was removed.");
this._removeDataItem(downloadGuid);
break;
}
// Multiple downloads have been removed. Iterate over known downloads
// and remove those that don't exist anymore.
DownloadsCommon.log("Multiple downloads were removed.");
for each (let dataItem in this.dataItems) {
if (dataItem) {
// Bug 449811 - We have to bind to the dataItem because Javascript
// doesn't do fresh let-bindings per loop iteration.
let dataItemBinding = dataItem;
Services.downloads.getDownloadByGUID(dataItemBinding.downloadGuid,
function(aStatus, aResult) {
if (aStatus == Components.results.NS_ERROR_NOT_AVAILABLE) {
DownloadsCommon.log("Removing download with id",
dataItemBinding.downloadGuid);
this._removeDataItem(dataItemBinding.downloadGuid);
}
}.bind(this));
}
}
break;
}
},
//////////////////////////////////////////////////////////////////////////////
//// nsIDownloadProgressListener
onDownloadStateChange: function DD_onDownloadStateChange(aOldState, aDownload)
{
if (aDownload.isPrivate != this._isPrivate) {
// Ignore the downloads with a privacy status other than what we are
// tracking.
return;
}
// When a new download is added, it may have the same identifier of a
// download that we previously deleted during this session, and we also
// want to provide a visible indication that the download started.
let isNew = aOldState == nsIDM.DOWNLOAD_NOTSTARTED ||
aOldState == nsIDM.DOWNLOAD_QUEUED;
let dataItem = this._getOrAddDataItem(aDownload, isNew);
if (!dataItem) {
return;
}
let wasInProgress = dataItem.inProgress;
DownloadsCommon.log("A download changed its state to:", aDownload.state);
dataItem.state = aDownload.state;
dataItem.referrer = aDownload.referrer && aDownload.referrer.spec;
dataItem.resumable = aDownload.resumable;
dataItem.startTime = Math.round(aDownload.startTime / 1000);
dataItem.currBytes = aDownload.amountTransferred;
dataItem.maxBytes = aDownload.size;
if (wasInProgress && !dataItem.inProgress) {
dataItem.endTime = Date.now();
}
// When a download is retried, we create a different download object from
// the database with the same ID as before. This means that the nsIDownload
// that the dataItem holds might now need updating.
//
// We only overwrite this in the event that _download exists, because if it
// doesn't, that means that no caller ever tried to get the nsIDownload,
// which means it was never retrieved and doesn't need to be overwritten.
if (dataItem._download) {
dataItem._download = aDownload;
}
for (let view of this._views) {
try {
view.getViewItem(dataItem).onStateChange(aOldState);
} catch (ex) {
Cu.reportError(ex);
}
}
if (isNew && !dataItem.newDownloadNotified) {
dataItem.newDownloadNotified = true;
this._notifyDownloadEvent("start");
}
// This is a final state of which we are only notified once.
if (dataItem.done) {
this._notifyDownloadEvent("finish");
}
// TODO Bug 830415: this isn't the right place to set these annotation.
// It should be set it in places' nsIDownloadHistory implementation.
if (!this._isPrivate && !dataItem.inProgress) {
let downloadMetaData = { state: dataItem.state,
endTime: dataItem.endTime };
if (dataItem.done)
downloadMetaData.fileSize = dataItem.maxBytes;
try {
PlacesUtils.annotations.setPageAnnotation(
NetUtil.newURI(dataItem.uri), "downloads/metaData", JSON.stringify(downloadMetaData), 0,
PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
}
catch(ex) {
Cu.reportError(ex);
}
}
},
onProgressChange: function DD_onProgressChange(aWebProgress, aRequest,
aCurSelfProgress,
aMaxSelfProgress,
aCurTotalProgress,
aMaxTotalProgress, aDownload)
{
if (aDownload.isPrivate != this._isPrivate) {
// Ignore the downloads with a privacy status other than what we are
// tracking.
return;
}
let dataItem = this._getOrAddDataItem(aDownload, false);
if (!dataItem) {
return;
}
dataItem.currBytes = aDownload.amountTransferred;
dataItem.maxBytes = aDownload.size;
dataItem.speed = aDownload.speed;
dataItem.percentComplete = aDownload.percentComplete;
this._views.forEach(
function (view) view.getViewItem(dataItem).onProgressChange()
);
},
onStateChange: function () { },
onSecurityChange: function () { },
//////////////////////////////////////////////////////////////////////////////
//// Notifications sent to the most recent browser window only
@ -1252,10 +769,6 @@ DownloadsDataCtor.prototype = {
_notifyDownloadEvent: function DD_notifyDownloadEvent(aType)
{
DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
if (DownloadsCommon.useToolkitUI) {
DownloadsCommon.log("Cancelling notification - we're using the toolkit downloads manager.");
return;
}
// Show the panel in the most recent browser window, if present.
let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate });
@ -1288,25 +801,24 @@ XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
//// DownloadsDataItem
/**
* Represents a single item in the list of downloads. This object either wraps
* an existing nsIDownload from the Download Manager, or provides the same
* information read directly from the downloads database, with the possibility
* of querying the nsIDownload lazily, for performance reasons.
* Represents a single item in the list of downloads.
*
* @param aSource
* Object containing the data with which the item should be initialized.
* This should implement either nsIDownload or mozIStorageRow. If the
* JavaScript API for downloads is enabled, this is a Download object.
* The endTime property is initialized to the current date and time.
*
* @param aDownload
* The Download object with the current state.
*/
function DownloadsDataItem(aSource)
function DownloadsDataItem(aDownload)
{
if (DownloadsCommon.useJSTransfer) {
this._initFromJSDownload(aSource);
} else if (aSource instanceof Ci.nsIDownload) {
this._initFromDownload(aSource);
} else {
this._initFromDataRow(aSource);
}
this._download = aDownload;
this.downloadGuid = "id:" + this._autoIncrementId;
this.file = aDownload.target.path;
this.target = OS.Path.basename(aDownload.target.path);
this.uri = aDownload.source.url;
this.endTime = Date.now();
this.updateFromDownload();
}
DownloadsDataItem.prototype = {
@ -1318,30 +830,9 @@ DownloadsDataItem.prototype = {
__lastId: 0,
/**
* Initializes this object from the JavaScript API for downloads.
*
* The endTime property is initialized to the current date and time.
*
* @param aDownload
* The Download object with the current state.
* Updates this object from the underlying Download object.
*/
_initFromJSDownload: function (aDownload)
{
this._download = aDownload;
this.downloadGuid = "id:" + this._autoIncrementId;
this.file = aDownload.target.path;
this.target = OS.Path.basename(aDownload.target.path);
this.uri = aDownload.source.url;
this.endTime = Date.now();
this.updateFromJSDownload();
},
/**
* Updates this object from the JavaScript API for downloads.
*/
updateFromJSDownload: function ()
updateFromDownload: function ()
{
// Collapse state using the correct priority.
if (this._download.succeeded) {
@ -1370,115 +861,6 @@ DownloadsDataItem.prototype = {
this.percentComplete = this._download.progress;
},
/**
* Initializes this object from a download object of the Download Manager.
*
* The endTime property is initialized to the current date and time.
*
* @param aDownload
* The nsIDownload with the current state.
*/
_initFromDownload: function DDI_initFromDownload(aDownload)
{
this._download = aDownload;
// Fetch all the download properties eagerly.
this.downloadGuid = aDownload.guid;
this.file = aDownload.target.spec;
this.target = aDownload.displayName;
this.uri = aDownload.source.spec;
this.referrer = aDownload.referrer && aDownload.referrer.spec;
this.state = aDownload.state;
this.startTime = Math.round(aDownload.startTime / 1000);
this.endTime = Date.now();
this.currBytes = aDownload.amountTransferred;
this.maxBytes = aDownload.size;
this.resumable = aDownload.resumable;
this.speed = aDownload.speed;
this.percentComplete = aDownload.percentComplete;
},
/**
* Initializes this object from a data row in the downloads database, without
* querying the associated nsIDownload object, to improve performance when
* loading the list of downloads asynchronously.
*
* When this object is initialized in this way, accessing the "download"
* property loads the underlying nsIDownload object synchronously, and should
* be avoided unless the object is really required.
*
* @param aStorageRow
* The mozIStorageRow from the downloads database.
*/
_initFromDataRow: function DDI_initFromDataRow(aStorageRow)
{
// Get the download properties from the data row.
this._download = null;
this.downloadGuid = aStorageRow.getResultByName("guid");
this.file = aStorageRow.getResultByName("target");
this.target = aStorageRow.getResultByName("name");
this.uri = aStorageRow.getResultByName("source");
this.referrer = aStorageRow.getResultByName("referrer");
this.state = aStorageRow.getResultByName("state");
this.startTime = Math.round(aStorageRow.getResultByName("startTime") / 1000);
this.endTime = Math.round(aStorageRow.getResultByName("endTime") / 1000);
this.currBytes = aStorageRow.getResultByName("currBytes");
this.maxBytes = aStorageRow.getResultByName("maxBytes");
// Now we have to determine if the download is resumable, but don't want to
// access the underlying download object unnecessarily. The only case where
// the property is relevant is when we are currently downloading data, and
// in this case the download object is already loaded in memory or will be
// loaded very soon in any case. In all the other cases, including a paused
// download, we assume that the download is resumable. The property will be
// updated as soon as the underlying download state changes.
// We'll start by assuming we're resumable, and then if we're downloading,
// update resumable property in case we were wrong.
this.resumable = true;
if (this.state == nsIDM.DOWNLOAD_DOWNLOADING) {
this.getDownload(function(aDownload) {
this.resumable = aDownload.resumable;
}.bind(this));
}
// Compute the other properties without accessing the download object.
this.speed = 0;
this.percentComplete = this.maxBytes <= 0
? -1
: Math.round(this.currBytes / this.maxBytes * 100);
},
/**
* Asynchronous getter for the download object corresponding to this data item.
*
* @param aCallback
* A callback function which will be called when the download object is
* available. It should accept one argument which will be the download
* object.
*/
getDownload: function DDI_getDownload(aCallback) {
if (this._download) {
// Return the download object asynchronously to the caller
let download = this._download;
Services.tm.mainThread.dispatch(function () aCallback(download),
Ci.nsIThread.DISPATCH_NORMAL);
} else {
Services.downloads.getDownloadByGUID(this.downloadGuid,
function(aStatus, aResult) {
if (!Components.isSuccessCode(aStatus)) {
Cu.reportError(
new Components.Exception("Cannot retrieve download for GUID: " +
this.downloadGuid));
} else {
this._download = aResult;
aCallback(aResult);
}
}.bind(this));
}
},
/**
* Indicates whether the download is proceeding normally, and not finished
* yet. This includes paused downloads. When this property is true, the
@ -1600,22 +982,9 @@ DownloadsDataItem.prototype = {
/**
* Open the target file for this download.
*
* @param aOwnerWindow
* The window with which the required action is associated.
* @throws if the file cannot be opened.
*/
openLocalFile: function DDI_openLocalFile(aOwnerWindow) {
if (DownloadsCommon.useJSTransfer) {
this._download.launch().then(null, Cu.reportError);
return;
}
this.getDownload(function(aDownload) {
DownloadsCommon.openDownloadedFile(this.localFile,
aDownload.MIMEInfo,
aOwnerWindow);
}.bind(this));
openLocalFile: function () {
this._download.launch().then(null, Cu.reportError);
},
/**
@ -1630,26 +999,11 @@ DownloadsDataItem.prototype = {
* @throws if the download is not resumable or if has already done.
*/
togglePauseResume: function DDI_togglePauseResume() {
if (DownloadsCommon.useJSTransfer) {
if (this._download.stopped) {
this._download.start();
} else {
this._download.cancel();
}
return;
if (this._download.stopped) {
this._download.start();
} else {
this._download.cancel();
}
if (!this.inProgress || !this.resumable)
throw new Error("The given download cannot be paused or resumed");
this.getDownload(function(aDownload) {
if (this.inProgress) {
if (this.paused)
aDownload.resume();
else
aDownload.pause();
}
}.bind(this));
},
/**
@ -1657,73 +1011,25 @@ DownloadsDataItem.prototype = {
* @throws if we cannot.
*/
retry: function DDI_retry() {
if (DownloadsCommon.useJSTransfer) {
this._download.start();
return;
}
if (!this.canRetry)
throw new Error("Cannot retry this download");
this.getDownload(function(aDownload) {
aDownload.retry();
});
},
/**
* Support function that deletes the local file for a download. This is
* used in cases where the Download Manager service doesn't delete the file
* from disk when cancelling. See bug 732924.
*/
_ensureLocalFileRemoved: function DDI__ensureLocalFileRemoved()
{
try {
let localFile = this.localFile;
if (localFile.exists()) {
localFile.remove(false);
}
} catch (ex) { }
this._download.start();
},
/**
* Cancels the download.
* @throws if the download is already done.
*/
cancel: function() {
if (DownloadsCommon.useJSTransfer) {
this._download.cancel();
this._download.removePartialData().then(null, Cu.reportError);
return;
}
if (!this.inProgress)
throw new Error("Cannot cancel this download");
this.getDownload(function (aDownload) {
aDownload.cancel();
this._ensureLocalFileRemoved();
}.bind(this));
this._download.cancel();
this._download.removePartialData().then(null, Cu.reportError);
},
/**
* Remove the download.
*/
remove: function DDI_remove() {
if (DownloadsCommon.useJSTransfer) {
Downloads.getList(Downloads.ALL)
.then(list => list.remove(this._download))
.then(() => this._download.finalize(true))
.then(null, Cu.reportError);
return;
}
this.getDownload(function (aDownload) {
if (this.inProgress) {
aDownload.cancel();
this._ensureLocalFileRemoved();
}
aDownload.remove();
}.bind(this));
Downloads.getList(Downloads.ALL)
.then(list => list.remove(this._download))
.then(() => this._download.finalize(true))
.then(null, Cu.reportError);
}
};
@ -1838,18 +1144,6 @@ const DownloadsViewPrototype = {
this._loading = false;
},
/**
* Called when the downloads database becomes unavailable (for example, we
* entered Private Browsing Mode and the database backend changed).
* References to existing data should be discarded.
*
* @note Subclasses should override this.
*/
onDataInvalidated: function DVP_onDataInvalidated()
{
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
@ -1968,16 +1262,6 @@ DownloadsIndicatorDataCtor.prototype = {
this._updateViews();
},
/**
* Called when the downloads database becomes unavailable (for example, we
* entered Private Browsing Mode and the database backend changed).
* References to existing data should be discarded.
*/
onDataInvalidated: function DID_onDataInvalidated()
{
this._itemCount = 0;
},
/**
* Called when a new download data item is available, either during the
* asynchronous data load or when a new download is started.
@ -2271,11 +1555,6 @@ DownloadsSummaryData.prototype = {
this._updateViews();
},
onDataInvalidated: function DSD_onDataInvalidated()
{
this._dataItems = [];
},
onDataItemAdded: function DSD_onDataItemAdded(aDataItem, aNewest)
{
if (aNewest) {

View File

@ -1,16 +1,13 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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/. */
* 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 component listens to notifications for startup, shutdown and session
* restore, controlling which downloads should be loaded from the database.
*
* To avoid affecting startup performance, this component monitors the current
* session restore state, but defers the actual downloads data manipulation
* until the Download Manager service is loaded.
* This component enables the JavaScript API for downloads at startup. This
* will eventually be removed when nsIDownloadManager will not be available
* anymore (bug 851471).
*/
"use strict";
@ -23,44 +20,18 @@ const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
"resource:///modules/DownloadsCommon.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
"@mozilla.org/browser/sessionstartup;1",
"nsISessionStartup");
const kObservedTopics = [
"sessionstore-windows-restored",
"sessionstore-browser-state-restored",
"download-manager-initialized",
"download-manager-change-retention",
"last-pb-context-exited",
"browser-lastwindow-close-granted",
"quit-application",
"profile-change-teardown",
];
/**
* CID of our implementation of nsIDownloadManagerUI.
* CID and Contract ID of our implementation of nsIDownloadManagerUI.
*/
const kDownloadsUICid = Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}");
/**
* Contract ID of the service implementing nsIDownloadManagerUI.
*/
const kDownloadsUIContractId = "@mozilla.org/download-manager-ui;1";
/**
* CID of the JavaScript implementation of nsITransfer.
* CID and Contract ID of the JavaScript implementation of nsITransfer.
*/
const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
/**
* Contract ID of the service implementing nsITransfer.
*/
const kTransferContractId = "@mozilla.org/transfer;1";
////////////////////////////////////////////////////////////////////////////////
@ -76,204 +47,31 @@ DownloadsStartup.prototype = {
//////////////////////////////////////////////////////////////////////////////
//// nsISupports
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
//////////////////////////////////////////////////////////////////////////////
//// nsIObserver
observe: function DS_observe(aSubject, aTopic, aData)
{
switch (aTopic) {
case "profile-after-change":
// Override Toolkit's nsIDownloadManagerUI implementation with our own.
// This must be done at application startup and not in the manifest to
// ensure that our implementation overrides the original one.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kDownloadsUICid, "",
kDownloadsUIContractId, null);
// 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":
case "sessionstore-browser-state-restored":
// Unless there is no saved session, there is a chance that we are
// starting up after a restart or a crash. We should check the disk
// database to see if there are completed downloads to recover and show
// in the panel, in addition to in-progress downloads.
if (gSessionStartup.sessionType != Ci.nsISessionStartup.NO_SESSION) {
this._restoringSession = true;
}
this._ensureDataLoaded();
break;
case "download-manager-initialized":
// Don't initialize the JavaScript data and user interface layer if we
// are initializing the Download Manager service during shutdown.
if (this._shuttingDown) {
break;
}
// Start receiving events for active and new downloads before we return
// from this observer function. We can't defer the execution of this
// step, to ensure that we don't lose events raised in the meantime.
DownloadsCommon.initializeAllDataLinks(
aSubject.QueryInterface(Ci.nsIDownloadManager));
this._downloadsServiceInitialized = true;
// Since this notification is generated during the getService call and
// we need to get the Download Manager service ourselves, we must post
// the handler on the event queue to be executed later.
Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this),
Ci.nsIThread.DISPATCH_NORMAL);
break;
case "download-manager-change-retention":
// If we're using the Downloads Panel, we override the retention
// preference to always retain downloads on completion.
if (!DownloadsCommon.useToolkitUI) {
aSubject.QueryInterface(Ci.nsISupportsPRInt32).data = 2;
}
break;
case "browser-lastwindow-close-granted":
// When using the panel interface, downloads that are already completed
// should be removed when the last full browser window is closed. This
// event is invoked only if the application is not shutting down yet.
// If the Download Manager service is not initialized, we don't want to
// initialize it just to clean up completed downloads, because they can
// be present only in case there was a browser crash or restart.
if (this._downloadsServiceInitialized &&
!DownloadsCommon.useToolkitUI) {
Services.downloads.cleanUp();
}
break;
case "last-pb-context-exited":
// Similar to the above notification, but for private downloads.
if (this._downloadsServiceInitialized &&
!DownloadsCommon.useToolkitUI) {
Services.downloads.cleanUpPrivate();
}
break;
case "quit-application":
// When the application is shutting down, we must free all resources in
// addition to cleaning up completed downloads. If the Download Manager
// service is not initialized, we don't want to initialize it just to
// clean up completed downloads, because they can be present only in
// case there was a browser crash or restart.
this._shuttingDown = true;
if (!this._downloadsServiceInitialized) {
break;
}
DownloadsCommon.terminateAllDataLinks();
// When using the panel interface, downloads that are already completed
// should be removed when quitting the application.
if (!DownloadsCommon.useToolkitUI && aData != "restart") {
this._cleanupOnShutdown = true;
}
break;
case "profile-change-teardown":
// If we need to clean up, we must do it synchronously after all the
// "quit-application" listeners are invoked, so that the Download
// Manager service has a chance to pause or cancel in-progress downloads
// before we remove completed downloads from the list. Note that, since
// "quit-application" was invoked, we've already exited Private Browsing
// Mode, thus we are always working on the disk database.
if (this._cleanupOnShutdown) {
Services.downloads.cleanUp();
}
if (!DownloadsCommon.useToolkitUI) {
// If we got this far, that means that we finished our first session
// with the Downloads Panel without crashing. This means that we don't
// have to force displaying only active downloads on the next startup
// now.
this._firstSessionCompleted = true;
}
break;
}
},
//////////////////////////////////////////////////////////////////////////////
//// Private
/**
* Indicates whether we're restoring a previous session. This is used by
* _recoverAllDownloads to determine whether or not we should load and
* display all downloads data, or restrict it to only the active downloads.
*/
_restoringSession: false,
/**
* Indicates whether the Download Manager service has been initialized. This
* flag is required because we want to avoid accessing the service immediately
* at browser startup. The service will start when the user first requests a
* download, or some time after browser startup.
*/
_downloadsServiceInitialized: false,
/**
* True while we are processing the "quit-application" event, and later.
*/
_shuttingDown: false,
/**
* True during shutdown if we need to remove completed downloads.
*/
_cleanupOnShutdown: false,
/**
* True if we should display all downloads, as opposed to just active
* downloads. We decide to display all downloads if we're restoring a session,
* or if we're using the Downloads Panel anytime after the first session with
* it has completed.
*/
get _recoverAllDownloads() {
return this._restoringSession ||
(!DownloadsCommon.useToolkitUI && this._firstSessionCompleted);
},
/**
* True if we've ever completed a session with the Downloads Panel enabled.
*/
get _firstSessionCompleted() {
return Services.prefs
.getBoolPref("browser.download.panel.firstSessionCompleted");
},
set _firstSessionCompleted(aValue) {
Services.prefs.setBoolPref("browser.download.panel.firstSessionCompleted",
aValue);
return aValue;
},
/**
* Ensures that persistent download data is reloaded at the appropriate time.
*/
_ensureDataLoaded: function DS_ensureDataLoaded()
{
if (!this._downloadsServiceInitialized) {
if (aTopic != "profile-after-change") {
Cu.reportError("Unexpected observer notification.");
return;
}
// If the previous session has been already restored, then we ensure that
// all the downloads are loaded. Otherwise, we only ensure that the active
// downloads from the previous session are loaded.
DownloadsCommon.ensureAllPersistentDataLoaded(!this._recoverAllDownloads);
}
// Override Toolkit's nsIDownloadManagerUI implementation with our own.
// This must be done at application startup and not in the manifest to
// ensure that our implementation overrides the original one.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kDownloadsUICid, "",
kDownloadsUIContractId, null);
// Override Toolkit's nsITransfer implementation with the one from the
// JavaScript API for downloads.
Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(kTransferCid, "",
kTransferContractId, null);
},
};
////////////////////////////////////////////////////////////////////////////////

View File

@ -1,15 +1,12 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=2 et sw=2 tw=80: */
/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
/* 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/. */
* 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 component implements the nsIDownloadManagerUI interface and opens the
* downloads panel in the most recent browser window when requested.
*
* If a specific preference is set, this component transparently forwards all
* calls to the original implementation in Toolkit, that shows the window UI.
* Downloads view for the most recent browser window when requested.
*/
"use strict";
@ -40,11 +37,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
function DownloadsUI()
{
XPCOMUtils.defineLazyGetter(this, "_toolkitUI", function () {
// Create Toolkit's nsIDownloadManagerUI implementation.
return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
.getService(Ci.nsIDownloadManagerUI);
});
}
DownloadsUI.prototype = {
@ -62,11 +54,6 @@ DownloadsUI.prototype = {
show: function DUI_show(aWindowContext, aDownload, aReason, aUsePrivateUI)
{
if (DownloadsCommon.useToolkitUI) {
this._toolkitUI.show(aWindowContext, aDownload, aReason, aUsePrivateUI);
return;
}
if (!aReason) {
aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
}
@ -92,27 +79,17 @@ DownloadsUI.prototype = {
}
},
get visible()
{
// If we're still using the toolkit downloads manager, delegate the call
// to it. Otherwise, return true for now, until we decide on how we want
// to indicate that a new download has started if a browser window is
// not available or minimized.
return DownloadsCommon.useToolkitUI ? this._toolkitUI.visible : true;
},
get visible() true,
getAttention: function DUI_getAttention()
{
if (DownloadsCommon.useToolkitUI) {
this._toolkitUI.getAttention();
}
},
getAttention: function () {},
//////////////////////////////////////////////////////////////////////////////
//// Private
/**
* Helper function that opens the download manager UI.
*/
_showDownloadManagerUI:
function DUI_showDownloadManagerUI(aWindowContext, aUsePrivateUI)
_showDownloadManagerUI: function (aWindowContext, aUsePrivateUI)
{
// If we weren't given a window context, try to find a browser window
// to use as our parent - and if that doesn't work, error out and give up.

View File

@ -1287,15 +1287,47 @@ BrowserGlue.prototype = {
_migrateUI: function BG__migrateUI() {
const UI_VERSION = 17;
const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
let wasCustomizedAndOnAustralis = Services.prefs.prefHasUserValue("browser.uiCustomization.state");
let currentUIVersion = 0;
try {
currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
} catch(ex) {}
if (currentUIVersion >= UI_VERSION)
if (!wasCustomizedAndOnAustralis && currentUIVersion >= UI_VERSION)
return;
this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
this._dataSource = this._rdf.GetDataSource("rdf:local-store");
// No version check for this as this code should run until we have Australis everywhere:
if (wasCustomizedAndOnAustralis) {
// This profile's been on australis! If it's missing the back/fwd button
// or go/stop/reload button, then put them back:
let currentsetResource = this._rdf.GetResource("currentset");
let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
let currentset = this._getPersist(toolbarResource, currentsetResource);
if (currentset.indexOf("unified-back-forward-button") == -1) {
currentset = currentset.replace("urlbar-container",
"unified-back-forward-button,urlbar-container");
}
if (currentset.indexOf("reload-button") == -1) {
currentset = currentset.replace("urlbar-container", "urlbar-container,reload-button");
}
if (currentset.indexOf("stop-button") == -1) {
currentset = currentset.replace("reload-button", "reload-button,stop-button");
}
this._setPersist(toolbarResource, currentsetResource, currentset);
Services.prefs.clearUserPref("browser.uiCustomization.state");
// If we don't have anything else to do, we can bail here:
if (currentUIVersion >= UI_VERSION) {
delete this._rdf;
delete this._dataSource;
return;
}
}
this._dirty = false;
if (currentUIVersion < 2) {

View File

@ -17,8 +17,6 @@ var gMainPane = {
this.updateBrowserStartupLastSession();
this.setupDownloadsWindowOptions();
// Notify observers that the UI is now ready
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
@ -39,17 +37,6 @@ var gMainPane = {
},
setupDownloadsWindowOptions: function ()
{
let showWhenDownloading = document.getElementById("showWhenDownloading");
let closeWhenDone = document.getElementById("closeWhenDone");
// These radio buttons should be hidden when the Downloads Panel is enabled.
let shouldHide = !DownloadsCommon.useToolkitUI;
showWhenDownloading.hidden = shouldHide;
closeWhenDone.hidden = shouldHide;
},
// HOME PAGE
/*
@ -201,12 +188,6 @@ var gMainPane = {
/*
* Preferences:
*
* browser.download.showWhenStarting - bool
* True if the Download Manager should be opened when a download is
* started, false if it shouldn't be opened.
* browser.download.closeWhenDone - bool
* True if the Download Manager should be closed when all downloads
* complete, false if it should be left open.
* browser.download.useDownloadDir - bool
* True - Save files directly to the folder configured via the
* browser.download.folderList preference.
@ -234,30 +215,6 @@ var gMainPane = {
* deprecated.
*/
/**
* Updates preferences which depend upon the value of the preference which
* determines whether the Downloads manager is opened at the start of a
* download.
*/
readShowDownloadsWhenStarting: function ()
{
this.showDownloadsWhenStartingPrefChanged();
// don't override the preference's value in UI
return undefined;
},
/**
* Enables or disables the "close Downloads manager when downloads finished"
* preference element, consequently updating the associated UI.
*/
showDownloadsWhenStartingPrefChanged: function ()
{
var showWhenStartingPref = document.getElementById("browser.download.manager.showWhenStarting");
var closeWhenDonePref = document.getElementById("browser.download.manager.closeWhenDone");
closeWhenDonePref.disabled = !showWhenStartingPref.value;
},
/**
* Enables/disables the folder field and Browse button based on whether a
* default download directory is being used.

View File

@ -31,13 +31,6 @@
onchange="gMainPane.updateBrowserStartupLastSession();"/>
<!-- Downloads -->
<preference id="browser.download.manager.showWhenStarting"
name="browser.download.manager.showWhenStarting"
type="bool"
onchange="gMainPane.showDownloadsWhenStartingPrefChanged();"/>
<preference id="browser.download.manager.closeWhenDone"
name="browser.download.manager.closeWhenDone"
type="bool"/>
<preference id="browser.download.useDownloadDir"
name="browser.download.useDownloadDir"
type="bool"/>
@ -160,19 +153,6 @@
<groupbox id="downloadsGroup" data-category="paneGeneral" hidden="true">
<caption label="&downloads.label;"/>
<checkbox id="showWhenDownloading"
label="&showWhenDownloading.label;"
accesskey="&showWhenDownloading.accesskey;"
preference="browser.download.manager.showWhenStarting"
onsyncfrompreference="return gMainPane.readShowDownloadsWhenStarting();"/>
<checkbox id="closeWhenDone"
label="&closeWhenDone.label;"
accesskey="&closeWhenDone.accesskey;"
class="indent"
preference="browser.download.manager.closeWhenDone"/>
<separator class="thin"/>
<radiogroup id="saveWhere"
preference="browser.download.useDownloadDir"
onsyncfrompreference="return gMainPane.readUseDownloadDir();">

View File

@ -23,25 +23,12 @@ var gMainPane = {
this.updateBrowserStartupLastSession();
this.setupDownloadsWindowOptions();
// Notify observers that the UI is now ready
Components.classes["@mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService)
.notifyObservers(window, "main-pane-loaded", null);
},
setupDownloadsWindowOptions: function ()
{
let showWhenDownloading = document.getElementById("showWhenDownloading");
let closeWhenDone = document.getElementById("closeWhenDone");
// These radio buttons should be hidden when the Downloads Panel is enabled.
let shouldHide = !DownloadsCommon.useToolkitUI;
showWhenDownloading.hidden = shouldHide;
closeWhenDone.hidden = shouldHide;
},
// HOME PAGE
/*
@ -185,12 +172,6 @@ var gMainPane = {
/*
* Preferences:
*
* browser.download.showWhenStarting - bool
* True if the Download Manager should be opened when a download is
* started, false if it shouldn't be opened.
* browser.download.closeWhenDone - bool
* True if the Download Manager should be closed when all downloads
* complete, false if it should be left open.
* browser.download.useDownloadDir - bool
* True - Save files directly to the folder configured via the
* browser.download.folderList preference.
@ -218,30 +199,6 @@ var gMainPane = {
* deprecated.
*/
/**
* Updates preferences which depend upon the value of the preference which
* determines whether the Downloads manager is opened at the start of a
* download.
*/
readShowDownloadsWhenStarting: function ()
{
this.showDownloadsWhenStartingPrefChanged();
// don't override the preference's value in UI
return undefined;
},
/**
* Enables or disables the "close Downloads manager when downloads finished"
* preference element, consequently updating the associated UI.
*/
showDownloadsWhenStartingPrefChanged: function ()
{
var showWhenStartingPref = document.getElementById("browser.download.manager.showWhenStarting");
var closeWhenDonePref = document.getElementById("browser.download.manager.closeWhenDone");
closeWhenDonePref.disabled = !showWhenStartingPref.value;
},
/**
* Enables/disables the folder field and Browse button based on whether a
* default download directory is being used.

View File

@ -50,13 +50,6 @@
onchange="gMainPane.updateBrowserStartupLastSession();"/>
<!-- Downloads -->
<preference id="browser.download.manager.showWhenStarting"
name="browser.download.manager.showWhenStarting"
type="bool"
onchange="gMainPane.showDownloadsWhenStartingPrefChanged();"/>
<preference id="browser.download.manager.closeWhenDone"
name="browser.download.manager.closeWhenDone"
type="bool"/>
<preference id="browser.download.useDownloadDir"
name="browser.download.useDownloadDir"
type="bool"/>
@ -117,16 +110,6 @@
<groupbox id="downloadsGroup">
<caption label="&downloads.label;"/>
<checkbox id="showWhenDownloading" label="&showWhenDownloading.label;"
accesskey="&showWhenDownloading.accesskey;"
preference="browser.download.manager.showWhenStarting"
onsyncfrompreference="return gMainPane.readShowDownloadsWhenStarting();"/>
<checkbox id="closeWhenDone" label="&closeWhenDone.label;"
accesskey="&closeWhenDone.accesskey;" class="indent"
preference="browser.download.manager.closeWhenDone"/>
<separator class="thin"/>
<radiogroup id="saveWhere"
preference="browser.download.useDownloadDir"
onsyncfrompreference="return gMainPane.readUseDownloadDir();">

View File

@ -12,17 +12,14 @@ const Ci = Components.interfaces;
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
// MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision.
const MAX_EXPIRY = Math.pow(2, 62);
// Creates a new nsIURI object.
function makeURI(uri) {
return Services.io.newURI(uri, null, null);
}
/**
* The external API implemented by the SessionCookies module.
*/
@ -172,7 +169,7 @@ let SessionCookiesInternal = {
// urls in which case we don't need to do anything.
if (!host && !scheme) {
try {
let uri = makeURI(entry.url);
let uri = Utils.makeURI(entry.url);
host = uri.host;
scheme = uri.scheme;
this._extractHostsFromHostScheme(host, scheme, hosts, checkPrivacy, isPinned);

View File

@ -4,7 +4,7 @@
"use strict";
this.EXPORTED_SYMBOLS = ["_SessionFile"];
this.EXPORTED_SYMBOLS = ["SessionFile"];
/**
* Implementation of all the disk I/O required by the session store.
@ -49,7 +49,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
"resource://gre/modules/Deprecated.jsm");
this._SessionFile = {
this.SessionFile = {
/**
* Read the contents of the session file, asynchronously.
*/
@ -100,7 +100,7 @@ this._SessionFile = {
}
};
Object.freeze(_SessionFile);
Object.freeze(SessionFile);
/**
* Utilities for dealing with promises and Task.jsm
@ -224,7 +224,7 @@ let SessionFileInternal = {
write: function (aData) {
if (this._isClosed) {
return Promise.reject(new Error("_SessionFile is closed"));
return Promise.reject(new Error("SessionFile is closed"));
}
let refObj = {};
@ -311,5 +311,5 @@ let SessionWorker = (function () {
AsyncShutdown.profileBeforeChange.addBlocker(
"SessionFile: Finish writing the latest sessionstore.js",
function() {
return _SessionFile._latestWrite;
return SessionFile._latestWrite;
});

View File

@ -109,7 +109,7 @@ let SessionMigration = {
return Task.spawn(function() {
let inState = yield SessionMigrationInternal.readState(aFromPath);
let outState = SessionMigrationInternal.convertState(inState);
// Unfortunately, we can't use SessionStore's own _SessionFile to
// Unfortunately, we can't use SessionStore's own SessionFile to
// write out the data because it has a dependency on the profile dir
// being known. When the migration runs, there is no guarantee that
// that's true.

View File

@ -18,8 +18,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
"resource:///modules/sessionstore/SessionStore.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
"resource:///modules/sessionstore/_SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
// Minimal interval between two save operations (in milliseconds).
XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
@ -290,7 +290,7 @@ let SessionSaverInternal = {
// Write (atomically) to a session file, using a tmp file. Once the session
// file is successfully updated, save the time stamp of the last save and
// notify the observers.
_SessionFile.write(data).then(() => {
SessionFile.write(data).then(() => {
this.updateLastSaveTime();
notify(null, "sessionstore-state-write-complete");
}, Cu.reportError);

View File

@ -99,41 +99,32 @@ XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
"@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
/**
* Get nsIURI from string
* @param string
* @returns nsIURI
*/
function makeURI(aString) {
return Services.io.newURI(aString, null, null);
}
XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
"resource:///modules/devtools/scratchpad-manager.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DocShellCapabilities",
"resource:///modules/sessionstore/DocShellCapabilities.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
"resource:///modules/sessionstore/DocumentUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
"resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PageStyle",
"resource:///modules/sessionstore/PageStyle.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
"resource:///modules/sessionstore/PrivacyLevel.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
"resource:///modules/sessionstore/SessionSaver.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
"resource:///modules/sessionstore/SessionStorage.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
"resource:///modules/sessionstore/SessionCookies.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionHistory",
"resource:///modules/sessionstore/SessionHistory.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TextAndScrollData",
"resource:///modules/sessionstore/TextAndScrollData.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
"resource:///modules/sessionstore/_SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabState",
"resource:///modules/sessionstore/TabState.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
"resource:///modules/sessionstore/TabStateCache.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Utils",
"resource:///modules/sessionstore/Utils.jsm");
#ifdef MOZ_CRASHREPORTER
XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
@ -499,12 +490,12 @@ let SessionStoreInternal = {
return Task.spawn(function task() {
try {
// Perform background backup
yield _SessionFile.createBackupCopy("-" + buildID);
yield SessionFile.createBackupCopy("-" + buildID);
this._prefBranch.setCharPref(PREF_UPGRADE, buildID);
// In case of success, remove previous backup.
yield _SessionFile.removeBackupCopy("-" + latestBackup);
yield SessionFile.removeBackupCopy("-" + latestBackup);
} catch (ex) {
debug("Could not perform upgrade backup " + ex);
debug(ex.stack);
@ -746,7 +737,7 @@ let SessionStoreInternal = {
// _loadState changed from "stopped" to "running". Save the session's
// load state immediately so that crashes happening during startup
// are correctly counted.
_SessionFile.writeLoadStateOnceAfterStartup(STATE_RUNNING_STR);
SessionFile.writeLoadStateOnceAfterStartup(STATE_RUNNING_STR);
}
}
else {
@ -1082,7 +1073,7 @@ let SessionStoreInternal = {
* On purge of session history
*/
onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
_SessionFile.wipe();
SessionFile.wipe();
// If the browser is shutting down, simply return after clearing the
// session data on disk as this notification fires after the
// quit-application notification so the browser is about to exit.
@ -1129,12 +1120,9 @@ let SessionStoreInternal = {
onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
// does a session history entry contain a url for the given domain?
function containsDomain(aEntry) {
try {
if (makeURI(aEntry.url).host.hasRootDomain(aData)) {
return true;
}
if (Utils.hasRootDomain(aEntry.url, aData)) {
return true;
}
catch (ex) { /* url had no host at all */ }
return aEntry.children && aEntry.children.some(containsDomain, this);
}
// remove all closed tabs containing a reference to the given domain
@ -2600,7 +2588,7 @@ let SessionStoreInternal = {
// the tab is restored. We'll reset this to about:blank when we try to
// restore the tab to ensure that docshell doeesn't get confused.
if (uri) {
browser.docShell.setCurrentURI(makeURI(uri));
browser.docShell.setCurrentURI(Utils.makeURI(uri));
}
// If the page has a title, set it.
@ -2776,7 +2764,7 @@ let SessionStoreInternal = {
// Reset currentURI. This creates a new session history entry with a new
// doc identifier, so we need to explicitly save and restore the old doc
// identifier (corresponding to the SHEntry at activeIndex) below.
browser.webNavigation.setCurrentURI(makeURI("about:blank"));
browser.webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
// Attach data that will be restored on "load" event, after tab is restored.
if (activeIndex > -1) {
// restore those aspects of the currently active documents which are not
@ -2868,7 +2856,7 @@ let SessionStoreInternal = {
var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
createInstance(Ci.nsISHEntry);
shEntry.setURI(makeURI(aEntry.url));
shEntry.setURI(Utils.makeURI(aEntry.url));
shEntry.setTitle(aEntry.title || aEntry.url);
if (aEntry.subframe)
shEntry.setIsSubFrame(aEntry.subframe || false);
@ -2876,7 +2864,7 @@ let SessionStoreInternal = {
if (aEntry.contentType)
shEntry.contentType = aEntry.contentType;
if (aEntry.referrer)
shEntry.referrerURI = makeURI(aEntry.referrer);
shEntry.referrerURI = Utils.makeURI(aEntry.referrer);
if (aEntry.isSrcdocEntry)
shEntry.srcdocData = aEntry.srcdocData;
@ -4088,52 +4076,6 @@ let DirtyWindows = {
// batch tab-closing operations.
let NumberOfTabsClosedLastPerWindow = new WeakMap();
// A set of tab attributes to persist. We will read a given list of tab
// attributes when collecting tab data and will re-set those attributes when
// the given tab data is restored to a new tab.
let TabAttributes = {
_attrs: new Set(),
// We never want to directly read or write those attributes.
// 'image' should not be accessed directly but handled by using the
// gBrowser.getIcon()/setIcon() methods.
// 'pending' is used internal by sessionstore and managed accordingly.
_skipAttrs: new Set(["image", "pending"]),
persist: function (name) {
if (this._attrs.has(name) || this._skipAttrs.has(name)) {
return false;
}
this._attrs.add(name);
return true;
},
get: function (tab) {
let data = {};
for (let name of this._attrs) {
if (tab.hasAttribute(name)) {
data[name] = tab.getAttribute(name);
}
}
return data;
},
set: function (tab, data = {}) {
// Clear attributes.
for (let name of this._attrs) {
tab.removeAttribute(name);
}
// Set attributes.
for (let name in data) {
tab.setAttribute(name, data[name]);
}
}
};
// This is used to help meter the number of restoring tabs. This is the control
// point for telling the next tab to restore. It gets attached to each gBrowser
// via gBrowser.addTabsProgressListener
@ -4195,413 +4137,6 @@ SessionStoreSHistoryListener.prototype = {
}
}
// see nsPrivateBrowsingService.js
String.prototype.hasRootDomain = function hasRootDomain(aDomain) {
let index = this.indexOf(aDomain);
if (index == -1)
return false;
if (this == aDomain)
return true;
let prevChar = this[index - 1];
return (index == (this.length - aDomain.length)) &&
(prevChar == "." || prevChar == "/");
};
function TabData(obj = null) {
if (obj) {
if (obj instanceof TabData) {
// FIXME: Can we get rid of this?
return obj;
}
for (let [key, value] in Iterator(obj)) {
this[key] = value;
}
}
return this;
}
/**
* Module that contains tab state collection methods.
*/
let TabState = {
// A map (xul:tab -> promise) that keeps track of tabs and
// their promises when collecting tab data asynchronously.
_pendingCollections: new WeakMap(),
// A map (xul:browser -> handler) that maps a tab to the
// synchronous collection handler object for that tab.
// See SyncHandler in content-sessionStore.js.
_syncHandlers: new WeakMap(),
/**
* Install the sync handler object from a given tab.
*/
setSyncHandler: function (browser, handler) {
this._syncHandlers.set(browser, handler);
},
/**
* When a docshell swap happens, a xul:browser element will be
* associated with a different content-sessionStore.js script
* global. In this case, the sync handler for the element needs to
* be swapped just like the docshell.
*/
onSwapDocShells: function (browser, otherBrowser) {
// Make sure that one or the other of these has a sync handler,
// and let it be |browser|.
if (!this._syncHandlers.has(browser)) {
[browser, otherBrowser] = [otherBrowser, browser];
if (!this._syncHandlers.has(browser)) {
return;
}
}
// At this point, browser is guaranteed to have a sync handler,
// although otherBrowser may not. Perform the swap.
let handler = this._syncHandlers.get(browser);
if (this._syncHandlers.has(otherBrowser)) {
let otherHandler = this._syncHandlers.get(otherBrowser);
this._syncHandlers.set(browser, otherHandler);
this._syncHandlers.set(otherHandler, handler);
} else {
this._syncHandlers.set(otherBrowser, handler);
this._syncHandlers.delete(browser);
}
},
/**
* Collect data related to a single tab, asynchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {Promise} A promise that will resolve to a TabData instance.
*/
collect: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
// Don't collect if we don't need to.
if (TabStateCache.has(tab)) {
return Promise.resolve(TabStateCache.get(tab));
}
// If the tab was recently added, or if it's being restored, we
// just collect basic data about it and skip the cache.
if (!this._tabNeedsExtraCollection(tab)) {
let tabData = this._collectBaseTabData(tab);
return Promise.resolve(tabData);
}
let promise = Task.spawn(function task() {
// Collect session history data asynchronously. Also collects
// text and scroll data.
let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
// Collected session storage data asynchronously.
let storage = yield Messenger.send(tab, "SessionStore:collectSessionStorage");
// Collect docShell capabilities asynchronously.
let disallow = yield Messenger.send(tab, "SessionStore:collectDocShellCapabilities");
let pageStyle = yield Messenger.send(tab, "SessionStore:collectPageStyle");
// Collect basic tab data, without session history and storage.
let tabData = this._collectBaseTabData(tab);
// Apply collected data.
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
// If we're still the latest async collection for the given tab and
// the cache hasn't been filled by collect() in the meantime, let's
// fill the cache with the data we received.
if (this._pendingCollections.get(tab) == promise) {
TabStateCache.set(tab, tabData);
this._pendingCollections.delete(tab);
}
throw new Task.Result(tabData);
}.bind(this));
// Save the current promise as the latest asynchronous collection that is
// running. This will be used to check whether the collected data is still
// valid and will be used to fill the tab state cache.
this._pendingCollections.set(tab, promise);
return promise;
},
/**
* Collect data related to a single tab, synchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {TabData} An object with the data for this tab. If the
* tab has not been invalidated since the last call to
* collectSync(aTab), the same object is returned.
*/
collectSync: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
if (TabStateCache.has(tab)) {
return TabStateCache.get(tab);
}
let tabData = this._collectSyncUncached(tab);
if (this._tabCachingAllowed(tab)) {
TabStateCache.set(tab, tabData);
}
// Prevent all running asynchronous collections from filling the cache.
// Every asynchronous data collection started before a collectSync() call
// can't expect to retrieve different data than the sync call. That's why
// we just fill the cache with the data collected from the sync call and
// discard any data collected asynchronously.
this.dropPendingCollections(tab);
return tabData;
},
/**
* Drop any pending calls to TabState.collect. These calls will
* continue to run, but they won't store their results in the
* TabStateCache.
*
* @param tab
* tabbrowser tab
*/
dropPendingCollections: function (tab) {
this._pendingCollections.delete(tab);
},
/**
* Collect data related to a single tab, including private data.
* Use with caution.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the data for this tab. This data is never
* cached, it will always be read from the tab and thus be
* up-to-date.
*/
clone: function (tab) {
return this._collectSyncUncached(tab, {includePrivateData: true});
},
/**
* Synchronously collect all session data for a tab. The
* TabStateCache is not consulted, and the resulting data is not put
* in the cache.
*/
_collectSyncUncached: function (tab, options = {}) {
// Collect basic tab data, without session history and storage.
let tabData = this._collectBaseTabData(tab);
// If we don't need any other data, return what we have.
if (!this._tabNeedsExtraCollection(tab)) {
return tabData;
}
// In multiprocess Firefox, there is a small window of time after
// tab creation when we haven't received a sync handler object. In
// this case the tab shouldn't have any history or cookie data, so we
// just return the base data already collected.
if (!this._syncHandlers.has(tab.linkedBrowser)) {
return tabData;
}
let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
let includePrivateData = options && options.includePrivateData;
let history, storage, disallow, pageStyle;
try {
history = syncHandler.collectSessionHistory(includePrivateData);
storage = syncHandler.collectSessionStorage();
disallow = syncHandler.collectDocShellCapabilities();
pageStyle = syncHandler.collectPageStyle();
} catch (e) {
// This may happen if the tab has crashed.
Cu.reportError(e);
return tabData;
}
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
return tabData;
},
/*
* Returns true if the xul:tab element is newly added (i.e., if it's
* showing about:blank with no history).
*/
_tabIsNew: function (tab) {
let browser = tab.linkedBrowser;
return (!browser || !browser.currentURI);
},
/*
* Returns true if the xul:tab element is in the process of being
* restored.
*/
_tabIsRestoring: function (tab) {
let browser = tab.linkedBrowser;
return (browser.__SS_data && browser.__SS_tabStillLoading);
},
/**
* This function returns true if we need to collect history, page
* style, and text and scroll data from the tab. Normally we do. The
* cases when we don't are:
* 1. the tab is about:blank with no history, or
* 2. the tab is waiting to be restored.
*
* @param tab A xul:tab element.
* @returns True if the tab is in the process of being restored.
*/
_tabNeedsExtraCollection: function (tab) {
if (this._tabIsNew(tab)) {
// Tab is about:blank with no history.
return false;
}
if (this._tabIsRestoring(tab)) {
// Tab is waiting to be restored.
return false;
}
// Otherwise we need the extra data.
return true;
},
/*
* Returns true if we should cache the tabData for the given the
* xul:tab element.
*/
_tabCachingAllowed: function (tab) {
if (this._tabIsNew(tab)) {
// No point in caching data for newly created tabs.
return false;
}
if (this._tabIsRestoring(tab)) {
// If the tab is being restored, we just return the data being
// restored. This data may be incomplete (if supplied by
// setBrowserState, for example), so we don't want to cache it.
return false;
}
return true;
},
/**
* Collects basic tab data for a given tab.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the basic data for this tab.
*/
_collectBaseTabData: function (tab) {
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
let browser = tab.linkedBrowser;
if (!browser || !browser.currentURI) {
// can happen when calling this function right after .addTab()
return tabData;
}
if (browser.__SS_data && browser.__SS_tabStillLoading) {
// Use the data to be restored when the tab hasn't been
// completely loaded. We clone the data, since we're updating it
// here and the caller may update it further.
tabData = JSON.parse(JSON.stringify(browser.__SS_data));
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// If __SS_extdata is set then we'll use that since it might be newer.
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
// If it exists but is empty then a key was likely deleted. In that case just
// delete extData.
if (tabData.extData && !Object.keys(tabData.extData).length)
delete tabData.extData;
return tabData;
}
// If there is a userTypedValue set, then either the user has typed something
// in the URL bar, or a new tab was opened with a URI to load. userTypedClear
// is used to indicate whether the tab was in some sort of loading state with
// userTypedValue.
if (browser.userTypedValue) {
tabData.userTypedValue = browser.userTypedValue;
tabData.userTypedClear = browser.userTypedClear;
} else {
delete tabData.userTypedValue;
delete tabData.userTypedClear;
}
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// Save tab attributes.
tabData.attributes = TabAttributes.get(tab);
// Store the tab icon.
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
tabData.image = tabbrowser.getIcon(tab);
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
else if (tabData.extData)
delete tabData.extData;
return tabData;
},
};
// The state from the previous session (after restoring pinned tabs). This
// state is persisted and passed through to the next session during an app
// restart to make the third party add-on warning not trash the deferred

View File

@ -74,14 +74,14 @@ let Agent = {
backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
/**
* This method is only intended to be called by _SessionFile.syncRead() and
* This method is only intended to be called by SessionFile.syncRead() and
* can be removed when we're not supporting synchronous SessionStore
* initialization anymore. When sessionstore.js is read from disk
* synchronously the state string must be supplied to the worker manually by
* calling this method.
*/
setInitialState: function (aState) {
// _SessionFile.syncRead() should not be called after startup has finished.
// SessionFile.syncRead() should not be called after startup has finished.
// Thus we also don't support any setInitialState() calls after we already
// wrote the loadState to disk.
if (this.hasWrittenLoadStateOnce) {
@ -89,7 +89,7 @@ let Agent = {
}
// Initial state might have been filled by read() already but yet we might
// be called by _SessionFile.syncRead() before SessionStore.jsm had a chance
// be called by SessionFile.syncRead() before SessionStore.jsm had a chance
// to call writeLoadStateOnceAfterStartup(). It's safe to ignore
// setInitialState() calls if this happens.
if (!this.initialState) {

View File

@ -0,0 +1,68 @@
/* 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 = ["TabAttributes"];
// A set of tab attributes to persist. We will read a given list of tab
// attributes when collecting tab data and will re-set those attributes when
// the given tab data is restored to a new tab.
this.TabAttributes = Object.freeze({
persist: function (name) {
return TabAttributesInternal.persist(name);
},
get: function (tab) {
return TabAttributesInternal.get(tab);
},
set: function (tab, data = {}) {
TabAttributesInternal.set(tab, data);
}
});
let TabAttributesInternal = {
_attrs: new Set(),
// We never want to directly read or write those attributes.
// 'image' should not be accessed directly but handled by using the
// gBrowser.getIcon()/setIcon() methods.
// 'pending' is used internal by sessionstore and managed accordingly.
_skipAttrs: new Set(["image", "pending"]),
persist: function (name) {
if (this._attrs.has(name) || this._skipAttrs.has(name)) {
return false;
}
this._attrs.add(name);
return true;
},
get: function (tab) {
let data = {};
for (let name of this._attrs) {
if (tab.hasAttribute(name)) {
data[name] = tab.getAttribute(name);
}
}
return data;
},
set: function (tab, data = {}) {
// Clear attributes.
for (let name of this._attrs) {
tab.removeAttribute(name);
}
// Set attributes.
for (let name in data) {
tab.setAttribute(name, data[name]);
}
}
};

View File

@ -0,0 +1,422 @@
/* 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 = ["TabState"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
"resource:///modules/sessionstore/Messenger.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
"resource:///modules/sessionstore/TabStateCache.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
"resource:///modules/sessionstore/TabAttributes.jsm");
/**
* Module that contains tab state collection methods.
*/
this.TabState = Object.freeze({
setSyncHandler: function (browser, handler) {
TabStateInternal.setSyncHandler(browser, handler);
},
collect: function (tab) {
return TabStateInternal.collect(tab);
},
collectSync: function (tab) {
return TabStateInternal.collectSync(tab);
},
clone: function (tab) {
return TabStateInternal.clone(tab);
},
dropPendingCollections: function (tab) {
TabStateInternal.dropPendingCollections(tab);
}
});
let TabStateInternal = {
// A map (xul:tab -> promise) that keeps track of tabs and
// their promises when collecting tab data asynchronously.
_pendingCollections: new WeakMap(),
// A map (xul:browser -> handler) that maps a tab to the
// synchronous collection handler object for that tab.
// See SyncHandler in content-sessionStore.js.
_syncHandlers: new WeakMap(),
/**
* Install the sync handler object from a given tab.
*/
setSyncHandler: function (browser, handler) {
this._syncHandlers.set(browser, handler);
},
/**
* When a docshell swap happens, a xul:browser element will be
* associated with a different content-sessionStore.js script
* global. In this case, the sync handler for the element needs to
* be swapped just like the docshell.
*/
onSwapDocShells: function (browser, otherBrowser) {
// Make sure that one or the other of these has a sync handler,
// and let it be |browser|.
if (!this._syncHandlers.has(browser)) {
[browser, otherBrowser] = [otherBrowser, browser];
if (!this._syncHandlers.has(browser)) {
return;
}
}
// At this point, browser is guaranteed to have a sync handler,
// although otherBrowser may not. Perform the swap.
let handler = this._syncHandlers.get(browser);
if (this._syncHandlers.has(otherBrowser)) {
let otherHandler = this._syncHandlers.get(otherBrowser);
this._syncHandlers.set(browser, otherHandler);
this._syncHandlers.set(otherHandler, handler);
} else {
this._syncHandlers.set(otherBrowser, handler);
this._syncHandlers.delete(browser);
}
},
/**
* Collect data related to a single tab, asynchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {Promise} A promise that will resolve to a TabData instance.
*/
collect: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
// Don't collect if we don't need to.
if (TabStateCache.has(tab)) {
return Promise.resolve(TabStateCache.get(tab));
}
// If the tab was recently added, or if it's being restored, we
// just collect basic data about it and skip the cache.
if (!this._tabNeedsExtraCollection(tab)) {
let tabData = this._collectBaseTabData(tab);
return Promise.resolve(tabData);
}
let promise = Task.spawn(function task() {
// Collect session history data asynchronously. Also collects
// text and scroll data.
let history = yield Messenger.send(tab, "SessionStore:collectSessionHistory");
// Collected session storage data asynchronously.
let storage = yield Messenger.send(tab, "SessionStore:collectSessionStorage");
// Collect docShell capabilities asynchronously.
let disallow = yield Messenger.send(tab, "SessionStore:collectDocShellCapabilities");
let pageStyle = yield Messenger.send(tab, "SessionStore:collectPageStyle");
// Collect basic tab data, without session history and storage.
let tabData = this._collectBaseTabData(tab);
// Apply collected data.
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
// If we're still the latest async collection for the given tab and
// the cache hasn't been filled by collect() in the meantime, let's
// fill the cache with the data we received.
if (this._pendingCollections.get(tab) == promise) {
TabStateCache.set(tab, tabData);
this._pendingCollections.delete(tab);
}
throw new Task.Result(tabData);
}.bind(this));
// Save the current promise as the latest asynchronous collection that is
// running. This will be used to check whether the collected data is still
// valid and will be used to fill the tab state cache.
this._pendingCollections.set(tab, promise);
return promise;
},
/**
* Collect data related to a single tab, synchronously.
*
* @param tab
* tabbrowser tab
*
* @returns {TabData} An object with the data for this tab. If the
* tab has not been invalidated since the last call to
* collectSync(aTab), the same object is returned.
*/
collectSync: function (tab) {
if (!tab) {
throw new TypeError("Expecting a tab");
}
if (TabStateCache.has(tab)) {
return TabStateCache.get(tab);
}
let tabData = this._collectSyncUncached(tab);
if (this._tabCachingAllowed(tab)) {
TabStateCache.set(tab, tabData);
}
// Prevent all running asynchronous collections from filling the cache.
// Every asynchronous data collection started before a collectSync() call
// can't expect to retrieve different data than the sync call. That's why
// we just fill the cache with the data collected from the sync call and
// discard any data collected asynchronously.
this.dropPendingCollections(tab);
return tabData;
},
/**
* Drop any pending calls to TabState.collect. These calls will
* continue to run, but they won't store their results in the
* TabStateCache.
*
* @param tab
* tabbrowser tab
*/
dropPendingCollections: function (tab) {
this._pendingCollections.delete(tab);
},
/**
* Collect data related to a single tab, including private data.
* Use with caution.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the data for this tab. This data is never
* cached, it will always be read from the tab and thus be
* up-to-date.
*/
clone: function (tab) {
return this._collectSyncUncached(tab, {includePrivateData: true});
},
/**
* Synchronously collect all session data for a tab. The
* TabStateCache is not consulted, and the resulting data is not put
* in the cache.
*/
_collectSyncUncached: function (tab, options = {}) {
// Collect basic tab data, without session history and storage.
let tabData = this._collectBaseTabData(tab);
// If we don't need any other data, return what we have.
if (!this._tabNeedsExtraCollection(tab)) {
return tabData;
}
// In multiprocess Firefox, there is a small window of time after
// tab creation when we haven't received a sync handler object. In
// this case the tab shouldn't have any history or cookie data, so we
// just return the base data already collected.
if (!this._syncHandlers.has(tab.linkedBrowser)) {
return tabData;
}
let syncHandler = this._syncHandlers.get(tab.linkedBrowser);
let includePrivateData = options && options.includePrivateData;
let history, storage, disallow, pageStyle;
try {
history = syncHandler.collectSessionHistory(includePrivateData);
storage = syncHandler.collectSessionStorage();
disallow = syncHandler.collectDocShellCapabilities();
pageStyle = syncHandler.collectPageStyle();
} catch (e) {
// This may happen if the tab has crashed.
Cu.reportError(e);
return tabData;
}
tabData.entries = history.entries;
if ("index" in history) {
tabData.index = history.index;
}
if (Object.keys(storage).length) {
tabData.storage = storage;
}
if (disallow.length > 0) {
tabData.disallow = disallow.join(",");
}
if (pageStyle) {
tabData.pageStyle = pageStyle;
}
return tabData;
},
/*
* Returns true if the xul:tab element is newly added (i.e., if it's
* showing about:blank with no history).
*/
_tabIsNew: function (tab) {
let browser = tab.linkedBrowser;
return (!browser || !browser.currentURI);
},
/*
* Returns true if the xul:tab element is in the process of being
* restored.
*/
_tabIsRestoring: function (tab) {
let browser = tab.linkedBrowser;
return (browser.__SS_data && browser.__SS_tabStillLoading);
},
/**
* This function returns true if we need to collect history, page
* style, and text and scroll data from the tab. Normally we do. The
* cases when we don't are:
* 1. the tab is about:blank with no history, or
* 2. the tab is waiting to be restored.
*
* @param tab A xul:tab element.
* @returns True if the tab is in the process of being restored.
*/
_tabNeedsExtraCollection: function (tab) {
if (this._tabIsNew(tab)) {
// Tab is about:blank with no history.
return false;
}
if (this._tabIsRestoring(tab)) {
// Tab is waiting to be restored.
return false;
}
// Otherwise we need the extra data.
return true;
},
/*
* Returns true if we should cache the tabData for the given the
* xul:tab element.
*/
_tabCachingAllowed: function (tab) {
if (this._tabIsNew(tab)) {
// No point in caching data for newly created tabs.
return false;
}
if (this._tabIsRestoring(tab)) {
// If the tab is being restored, we just return the data being
// restored. This data may be incomplete (if supplied by
// setBrowserState, for example), so we don't want to cache it.
return false;
}
return true;
},
/**
* Collects basic tab data for a given tab.
*
* @param tab
* tabbrowser tab
*
* @returns {object} An object with the basic data for this tab.
*/
_collectBaseTabData: function (tab) {
let tabData = {entries: [], lastAccessed: tab.lastAccessed };
let browser = tab.linkedBrowser;
if (!browser || !browser.currentURI) {
// can happen when calling this function right after .addTab()
return tabData;
}
if (browser.__SS_data && browser.__SS_tabStillLoading) {
// Use the data to be restored when the tab hasn't been
// completely loaded. We clone the data, since we're updating it
// here and the caller may update it further.
tabData = JSON.parse(JSON.stringify(browser.__SS_data));
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// If __SS_extdata is set then we'll use that since it might be newer.
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
// If it exists but is empty then a key was likely deleted. In that case just
// delete extData.
if (tabData.extData && !Object.keys(tabData.extData).length)
delete tabData.extData;
return tabData;
}
// If there is a userTypedValue set, then either the user has typed something
// in the URL bar, or a new tab was opened with a URI to load. userTypedClear
// is used to indicate whether the tab was in some sort of loading state with
// userTypedValue.
if (browser.userTypedValue) {
tabData.userTypedValue = browser.userTypedValue;
tabData.userTypedClear = browser.userTypedClear;
} else {
delete tabData.userTypedValue;
delete tabData.userTypedClear;
}
if (tab.pinned)
tabData.pinned = true;
else
delete tabData.pinned;
tabData.hidden = tab.hidden;
// Save tab attributes.
tabData.attributes = TabAttributes.get(tab);
// Store the tab icon.
let tabbrowser = tab.ownerDocument.defaultView.gBrowser;
tabData.image = tabbrowser.getIcon(tab);
if (tab.__SS_extdata)
tabData.extData = tab.__SS_extdata;
else if (tabData.extData)
delete tabData.extData;
return tabData;
}
};

View File

@ -0,0 +1,39 @@
/* 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 = ["Utils"];
const Cu = Components.utils;
Cu.import("resource://gre/modules/Services.jsm", this);
this.Utils = Object.freeze({
makeURI: function (url) {
return Services.io.newURI(url, null, null);
},
hasRootDomain: function (url, domain) {
let host;
try {
host = this.makeURI(url).host;
} catch (e) {
// The given URL probably doesn't have a host.
return false;
}
let index = host.indexOf(domain);
if (index == -1)
return false;
if (host == domain)
return true;
let prevChar = host[index - 1];
return (index == (host.length - domain.length)) &&
(prevChar == "." || prevChar == "/");
}
});

View File

@ -13,7 +13,6 @@ EXTRA_COMPONENTS += [
JS_MODULES_PATH = 'modules/sessionstore'
EXTRA_JS_MODULES = [
'_SessionFile.jsm',
'DocShellCapabilities.jsm',
'DocumentUtils.jsm',
'Messenger.jsm',
@ -21,12 +20,16 @@ EXTRA_JS_MODULES = [
'PrivacyLevel.jsm',
'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
'SessionCookies.jsm',
'SessionFile.jsm',
'SessionHistory.jsm',
'SessionMigration.jsm',
'SessionStorage.jsm',
'SessionWorker.js',
'TabAttributes.jsm',
'TabState.jsm',
'TabStateCache.jsm',
'TextAndScrollData.jsm',
'Utils.jsm',
'XPathGenerator.jsm',
]

View File

@ -43,8 +43,8 @@ Cu.import("resource://gre/modules/TelemetryStopwatch.jsm");
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
Cu.import("resource://gre/modules/Promise.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
"resource:///modules/sessionstore/_SessionFile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
"resource:///modules/sessionstore/SessionFile.jsm");
const STATE_RUNNING_STR = "running";
@ -80,7 +80,7 @@ SessionStartup.prototype = {
return;
}
_SessionFile.read().then(
SessionFile.read().then(
this._onSessionFileRead.bind(this),
Cu.reportError
);
@ -284,7 +284,7 @@ SessionStartup.prototype = {
// Initialization is complete, nothing else to do
return;
}
let contents = _SessionFile.syncRead();
let contents = SessionFile.syncRead();
this._onSessionFileRead(contents);
} catch(ex) {
debug("ensureInitialized: could not read session " + ex + ", " + ex.stack);

View File

@ -7,9 +7,9 @@
let tmp = {};
Cu.import("resource://gre/modules/osfile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
const {OS, _SessionFile} = tmp;
const {OS, SessionFile} = tmp;
const PREF_SS_INTERVAL = "browser.sessionstore.interval";
// Full paths for sessionstore.js and sessionstore.bak.
@ -100,29 +100,29 @@ function testReadBackup() {
array = yield OS.File.read(path);
gSSData = gDecoder.decode(array);
// Read sessionstore.js with _SessionFile.read.
let ssDataRead = yield _SessionFile.read();
is(ssDataRead, gSSData, "_SessionFile.read read sessionstore.js correctly.");
// Read sessionstore.js with SessionFile.read.
let ssDataRead = yield SessionFile.read();
is(ssDataRead, gSSData, "SessionFile.read read sessionstore.js correctly.");
// Read sessionstore.js with _SessionFile.syncRead.
ssDataRead = _SessionFile.syncRead();
// Read sessionstore.js with SessionFile.syncRead.
ssDataRead = SessionFile.syncRead();
is(ssDataRead, gSSData,
"_SessionFile.syncRead read sessionstore.js correctly.");
"SessionFile.syncRead read sessionstore.js correctly.");
// Remove sessionstore.js to test fallback onto sessionstore.bak.
yield OS.File.remove(path);
ssExists = yield OS.File.exists(path);
ok(!ssExists, "sessionstore.js should be removed now.");
// Read sessionstore.bak with _SessionFile.read.
ssDataRead = yield _SessionFile.read();
// Read sessionstore.bak with SessionFile.read.
ssDataRead = yield SessionFile.read();
is(ssDataRead, gSSBakData,
"_SessionFile.read read sessionstore.bak correctly.");
"SessionFile.read read sessionstore.bak correctly.");
// Read sessionstore.bak with _SessionFile.syncRead.
ssDataRead = _SessionFile.syncRead();
// Read sessionstore.bak with SessionFile.syncRead.
ssDataRead = SessionFile.syncRead();
is(ssDataRead, gSSBakData,
"_SessionFile.syncRead read sessionstore.bak correctly.");
"SessionFile.syncRead read sessionstore.bak correctly.");
nextTest(testBackupUnchanged);
}

View File

@ -7,7 +7,7 @@ Cu.import("resource://gre/modules/osfile.jsm");
function run_test() {
do_get_profile();
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", toplevel);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel);
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
run_next_test();
}
@ -19,7 +19,7 @@ function pathBackup(ext) {
// Ensure that things proceed smoothly if there is no file to back up
add_task(function test_nothing_to_backup() {
yield _SessionFile.createBackupCopy("");
yield SessionFile.createBackupCopy("");
});
// Create a file, back it up, remove it
@ -29,14 +29,14 @@ add_task(function test_do_backup() {
yield OS.File.writeAtomic(pathStore, content, {tmpPath: pathStore + ".tmp"});
do_print("Ensuring that the backup is created");
yield _SessionFile.createBackupCopy(ext);
yield SessionFile.createBackupCopy(ext);
do_check_true((yield OS.File.exists(pathBackup(ext))));
let data = yield OS.File.read(pathBackup(ext));
do_check_eq((new TextDecoder()).decode(data), content);
do_print("Ensuring that we can remove the backup");
yield _SessionFile.removeBackupCopy(ext);
yield SessionFile.removeBackupCopy(ext);
do_check_false((yield OS.File.exists(pathBackup(ext))));
});

View File

@ -6,7 +6,7 @@ Cu.import("resource://gre/modules/osfile.jsm");
function run_test() {
let profd = do_get_profile();
Cu.import("resource:///modules/sessionstore/_SessionFile.jsm", toplevel);
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", toplevel);
decoder = new TextDecoder();
pathStore = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js");
pathBackup = OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak");
@ -25,7 +25,7 @@ add_task(function test_first_write_backup() {
let initial_content = decoder.decode(yield OS.File.read(pathStore));
do_check_true(!(yield OS.File.exists(pathBackup)));
yield _SessionFile.write(content);
yield SessionFile.write(content);
do_check_true(yield OS.File.exists(pathBackup));
let backup_content = decoder.decode(yield OS.File.read(pathBackup));
@ -38,7 +38,7 @@ add_task(function test_second_write_no_backup() {
let initial_content = decoder.decode(yield OS.File.read(pathStore));
let initial_backup_content = decoder.decode(yield OS.File.read(pathBackup));
yield _SessionFile.write(content);
yield SessionFile.write(content);
let written_content = decoder.decode(yield OS.File.read(pathStore));
do_check_eq(content, written_content);

View File

@ -9,9 +9,11 @@ Cu.import("resource:///modules/devtools/gDevTools.jsm");
const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
const {require} = devtools;
const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
const {ConnectionManager, Connection}
= require("devtools/client/connection-manager");
const {getDeviceFront} = require("devtools/server/actors/device");
const {getTargetForApp} = require("devtools/app-actor-front");
const {getTargetForApp, launchApp, closeApp}
= require("devtools/app-actor-front");
const DeviceStore = require("devtools/app-manager/device-store");
const WebappsStore = require("devtools/app-manager/webapps-store");
const promise = require("sdk/core/promise");
@ -123,6 +125,8 @@ let UI = {
}
},
get connected() { return !!this.listTabsResponse; },
setTab: function(name) {
var tab = document.querySelector(".tab.selected");
var panel = document.querySelector(".tabpanel.selected");
@ -138,9 +142,9 @@ let UI = {
},
screenshot: function() {
if (!this.listTabsResponse)
if (!this.connected) {
return;
}
let front = getDeviceFront(this.connection.client, this.listTabsResponse);
front.screenshotToBlob().then(blob => {
let topWindow = Services.wm.getMostRecentWindow("navigator:browser");
@ -155,8 +159,9 @@ let UI = {
},
openToolbox: function(manifest) {
if (!this.listTabsResponse)
if (!this.connected) {
return;
}
getTargetForApp(this.connection.client,
this.listTabsResponse.webappsActor,
manifest).then((target) => {
@ -171,41 +176,21 @@ let UI = {
},
startApp: function(manifest) {
let deferred = promise.defer();
if (!this.listTabsResponse) {
deferred.reject();
} else {
let actor = this.listTabsResponse.webappsActor;
let request = {
to: actor,
type: "launch",
manifestURL: manifest,
}
this.connection.client.request(request, (res) => {
deferred.resolve()
});
if (!this.connected) {
return promise.reject();
}
return deferred.promise;
return launchApp(this.connection.client,
this.listTabsResponse.webappsActor,
manifest);
},
stopApp: function(manifest) {
let deferred = promise.defer();
if (!this.listTabsResponse) {
deferred.reject();
} else {
let actor = this.listTabsResponse.webappsActor;
let request = {
to: actor,
type: "close",
manifestURL: manifest,
}
this.connection.client.request(request, (res) => {
deferred.resolve()
});
if (!this.connected) {
return promise.reject();
}
return deferred.promise;
return closeApp(this.connection.client,
this.listTabsResponse.webappsActor,
manifest);
},
}

View File

@ -14,7 +14,8 @@ const {AppProjects} = require("devtools/app-manager/app-projects");
const {AppValidator} = require("devtools/app-manager/app-validator");
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
const {installHosted, installPackaged, getTargetForApp, reloadApp} = require("devtools/app-actor-front");
const {installHosted, installPackaged, getTargetForApp,
reloadApp, launchApp, closeApp} = require("devtools/app-actor-front");
const {EventEmitter} = Cu.import("resource:///modules/devtools/shared/event-emitter.js");
const promise = require("sdk/core/promise");
@ -206,6 +207,9 @@ let UI = {
},
reload: function (project) {
if (!this.connected) {
return promise.reject();
}
return reloadApp(this.connection.client,
this.listTabsResponse.webappsActor,
this._getProjectManifestURL(project)).
@ -253,6 +257,9 @@ let UI = {
},
install: function(project) {
if (!this.connected) {
return promise.reject();
}
this.connection.log("Installing the " + project.manifest.name + " app...");
let installPromise;
if (project.type == "packaged") {
@ -285,36 +292,30 @@ let UI = {
},
start: function(project) {
let deferred = promise.defer();
let request = {
to: this.listTabsResponse.webappsActor,
type: "launch",
manifestURL: this._getProjectManifestURL(project)
};
this.connection.client.request(request, (res) => {
if (res.error)
deferred.reject(res.error);
else
deferred.resolve(res);
});
return deferred.promise;
if (!this.connected) {
return promise.reject();
}
let manifestURL = this._getProjectManifestURL(project);
return launchApp(this.connection.client,
this.listTabsResponse.webappsActor,
manifestURL);
},
stop: function(location) {
if (!this.connected) {
return promise.reject();
}
let project = AppProjects.get(location);
let deferred = promise.defer();
let request = {
to: this.listTabsResponse.webappsActor,
type: "close",
manifestURL: this._getProjectManifestURL(project)
};
this.connection.client.request(request, (res) => {
promive.resolve(res);
});
return deferred.promise;
let manifestURL = this._getProjectManifestURL(project);
return closeApp(this.connection.client,
this.listTabsResponse.webappsActor,
manifestURL);
},
debug: function(button, location) {
if (!this.connected) {
return promise.reject();
}
button.disabled = true;
let project = AppProjects.get(location);

View File

@ -22,10 +22,6 @@
<!ENTITY downloads.label "Downloads">
<!ENTITY showWhenDownloading.label "Show the Downloads window when downloading a file">
<!ENTITY showWhenDownloading.accesskey "D">
<!ENTITY closeWhenDone.label "Close it when all downloads are finished">
<!ENTITY closeWhenDone.accesskey "w">
<!ENTITY saveTo.label "Save files to">
<!ENTITY saveTo.accesskey "v">
<!ENTITY chooseFolderWin.label "Browse…">

View File

@ -11,7 +11,6 @@ ifdef MOZILLA_OFFICIAL
DEFINES += -DMOZILLA_OFFICIAL
endif
GRE_MILESTONE := $(shell tail -n 1 $(topsrcdir)/config/milestone.txt 2>/dev/null || tail -1 $(topsrcdir)/config/milestone.txt)
GRE_BUILDID := $(shell cat $(DEPTH)/config/buildid)
DEFINES += -DGRE_MILESTONE=$(GRE_MILESTONE) -DGRE_BUILDID=$(GRE_BUILDID)

View File

@ -277,6 +277,21 @@ var ContextCommands = {
return null;
},
getPageSource: function cc_getPageSource() {
let uri = Services.io.newURI(Browser.selectedBrowser.currentURI.spec, null, null);
if (!uri.schemeIs("view-source")) {
return "view-source:" + Browser.selectedBrowser.currentURI.spec;
}
return null;
},
viewPageSource: function cc_viewPageSource() {
let uri = this.getPageSource();
if (uri) {
BrowserUI.addAndShowTab(uri);
}
},
/*
* Utilities
*/

View File

@ -163,9 +163,11 @@ const WebProgress = {
_showProgressBar: function (aTab) {
// display the track
Elements.progressContainer.removeAttribute("collapsed");
Elements.progress.style.width = aTab._progressCount + "%";
Elements.progress.removeAttribute("fade");
if (aTab == Browser.selectedTab) {
Elements.progressContainer.removeAttribute("collapsed");
Elements.progress.style.width = aTab._progressCount + "%";
Elements.progress.removeAttribute("fade");
}
// Create a pulse timer to keep things moving even if we don't
// collect any state changes.
@ -190,7 +192,9 @@ const WebProgress = {
if (!aTab._progressActive)
return;
this._stepProgressCount(aTab);
Elements.progress.style.width = aTab._progressCount + "%";
if (aTab == Browser.selectedTab) {
Elements.progress.style.width = aTab._progressCount + "%";
}
},
_progressStepTimer: function _progressStepTimer(aTab) {
@ -206,8 +210,10 @@ const WebProgress = {
_progressStop: function _progressStop(aJson, aTab) {
aTab._progressActive = false;
// 'Whoosh out' and fade
Elements.progress.style.width = "100%";
Elements.progress.setAttribute("fade", true);
if (aTab == Browser.selectedTab) {
Elements.progress.style.width = "100%";
Elements.progress.setAttribute("fade", true);
}
},
_progressTransEnd: function _progressTransEnd(aEvent) {

View File

@ -117,6 +117,8 @@ var Appbar = {
if (!BrowserUI.isStartTabVisible)
typesArray.push("find-in-page");
if (ContextCommands.getPageSource())
typesArray.push("view-page-source");
if (ContextCommands.getStoreLink())
typesArray.push("ms-meta-data");
if (ConsolePanelView.enabled)

View File

@ -792,6 +792,9 @@
<richlistitem id="context-msmetadata" type="ms-meta-data" onclick="ContextCommands.openWindowsStoreLink();">
<label value="&appbarMSMetaData2.label;"/>
</richlistitem>
<richlistitem id="context-viewpagesource" type="view-page-source" onclick="ContextCommands.viewPageSource();">
<label value="&appbarViewPageSource.label;"/>
</richlistitem>
</richlistbox>
</vbox>
</box>

View File

@ -254,7 +254,7 @@ var MetroDownloadsView = {
.replace("#1", this._downloadCount)
} else {
let runButtonText =
Strings.browser.GetStringFromName("downloadRun");
Strings.browser.GetStringFromName("downloadOpen");
message = Strings.browser.formatStringFromName("alertDownloadsDone2",
[this._lastDownload.displayName], 1);
@ -299,7 +299,7 @@ var MetroDownloadsView = {
} else {
title = Strings.browser.formatStringFromName("alertDownloadsDone",
[this._lastDownload.displayName], 1);
msg = Strings.browser.GetStringFromName("downloadRunNow");
msg = Strings.browser.GetStringFromName("downloadOpenNow");
observer = {
observe: function (aSubject, aTopic, aData) {
switch (aTopic) {

View File

@ -18,6 +18,12 @@ XPCOMUtils.defineLazyGetter(this, "ContentUtil", function() {
Cu.import("resource:///modules/ContentUtil.jsm");
return ContentUtil;
});
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
"resource://gre/modules/Downloads.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
"resource://gre/modules/FileUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Task",
"resource://gre/modules/Task.jsm");
// -----------------------------------------------------------------------
// HelperApp Launcher Dialog
@ -68,7 +74,7 @@ HelperAppLauncherDialog.prototype = {
let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
let runButtonText =
browserBundle.GetStringFromName("downloadRun");
browserBundle.GetStringFromName("downloadOpen");
let saveButtonText =
browserBundle.GetStringFromName("downloadSave");
let cancelButtonText =
@ -105,7 +111,7 @@ HelperAppLauncherDialog.prototype = {
let document = notificationBox.ownerDocument;
downloadSize = this._getDownloadSize(aLauncher.contentLength);
let msg = browserBundle.GetStringFromName("alertDownloadSave");
let msg = browserBundle.GetStringFromName("alertDownloadSave2");
let fragment = ContentUtil.populateFragmentFromString(
document.createDocumentFragment(),
@ -133,98 +139,109 @@ HelperAppLauncherDialog.prototype = {
},
promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
throw new Components.Exception("Async version must be used", Cr.NS_ERROR_NOT_AVAILABLE);
},
promptForSaveToFileAsync: function hald_promptForSaveToFileAsync(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
let file = null;
let prefs = Services.prefs;
if (!aForcePrompt) {
// Check to see if the user wishes to auto save to the default download
// folder without prompting. Note that preference might not be set.
let autodownload = true;
try {
autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
} catch (e) { }
if (autodownload) {
// Retrieve the user's default download directory
let dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
let defaultFolder = dnldMgr.userDownloadsDirectory;
Task.spawn(function() {
if (!aForcePrompt) {
// Check to see if the user wishes to auto save to the default download
// folder without prompting. Note that preference might not be set.
let autodownload = true;
try {
file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
}
catch (e) {
}
autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
} catch (e) { }
// Check to make sure we have a valid directory, otherwise, prompt
if (file)
return file;
if (autodownload) {
// Retrieve the user's preferred download directory
let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
let defaultFolder = new FileUtils.File(preferredDir);
try {
file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt);
}
catch (e) {
}
// Check to make sure we have a valid directory, otherwise, prompt
if (file) {
aLauncher.saveDestinationAvailable(file);
return;
}
}
}
}
// Use file picker to show dialog.
let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let windowTitle = "";
let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave);
picker.defaultString = aDefaultFile;
// Use file picker to show dialog.
let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
let windowTitle = "";
let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave);
picker.defaultString = aDefaultFile;
if (aSuggestedFileExt) {
// aSuggestedFileExtension includes the period, so strip it
picker.defaultExtension = aSuggestedFileExt.substring(1);
}
else {
if (aSuggestedFileExt) {
// aSuggestedFileExtension includes the period, so strip it
picker.defaultExtension = aSuggestedFileExt.substring(1);
}
else {
try {
picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension;
}
catch (e) { }
}
let wildCardExtension = "*";
if (aSuggestedFileExt) {
wildCardExtension += aSuggestedFileExt;
picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension);
}
picker.appendFilters(Ci.nsIFilePicker.filterAll);
// Default to lastDir if it is valid, otherwise use the user's preferred
// downloads directory. getPreferredDownloadsDirectory should always
// return a valid directory string, so we can safely default to it.
let preferredDir = yield Downloads.getPreferredDownloadsDirectory();
picker.displayDirectory = new FileUtils.File(preferredDir);
// The last directory preference may not exist, which will throw.
try {
picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension;
let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
if (isUsableDirectory(lastDir))
picker.displayDirectory = lastDir;
}
catch (e) { }
}
var wildCardExtension = "*";
if (aSuggestedFileExt) {
wildCardExtension += aSuggestedFileExt;
picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension);
}
picker.open(function(aResult) {
if (aResult == Ci.nsIFilePicker.returnCancel) {
// null result means user cancelled.
aLauncher.saveDestinationAvailable(null);
return;
}
picker.appendFilters(Ci.nsIFilePicker.filterAll);
// Be sure to save the directory the user chose through the Save As...
// dialog as the new browser.download.dir since the old one
// didn't exist.
file = picker.file;
// Default to lastDir if it is valid, otherwise use the user's default
// downloads directory. userDownloadsDirectory should always return a
// valid directory, so we can safely default to it.
var dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
picker.displayDirectory = dnldMgr.userDownloadsDirectory;
// The last directory preference may not exist, which will throw.
try {
let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile);
if (isUsableDirectory(lastDir))
picker.displayDirectory = lastDir;
}
catch (e) { }
if (picker.show() == Ci.nsIFilePicker.returnCancel) {
// null result means user cancelled.
return null;
}
// Be sure to save the directory the user chose through the Save As...
// dialog as the new browser.download.dir since the old one
// didn't exist.
file = picker.file;
if (file) {
try {
// Remove the file so that it's not there when we ensure non-existence later;
// this is safe because for the file to exist, the user would have had to
// confirm that he wanted the file overwritten.
if (file.exists())
file.remove(false);
}
catch (e) { }
var newDir = file.parent.QueryInterface(Ci.nsILocalFile);
prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir);
file = this.validateLeafName(newDir, file.leafName, null);
}
return file;
if (file) {
try {
// Remove the file so that it's not there when we ensure non-existence later;
// this is safe because for the file to exist, the user would have had to
// confirm that he wanted the file overwritten.
if (file.exists())
file.remove(false);
}
catch (e) { }
let newDir = file.parent.QueryInterface(Ci.nsILocalFile);
prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir);
file = this.validateLeafName(newDir, file.leafName, null);
}
aLauncher.saveDestinationAvailable(file);
}.bind(this));
}.bind(this));
},
validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) {

View File

@ -1,6 +1,6 @@
# 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 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/. */
const Ci = Components.interfaces;
const Cc = Components.classes;

View File

@ -13,11 +13,15 @@ MODULE = 'components'
# metro/components.manifest
EXTRA_COMPONENTS += [
'AlertsService.js',
'BrowserStartup.js',
'ContentDispatchChooser.js',
'ContentPermissionPrompt.js',
'DirectoryProvider.js',
'DownloadManagerUI.js',
'HelperAppDialog.js',
'LoginManagerPrompter.js',
'PromptService.js',
'Sidebar.js',
]
if CONFIG['MOZ_SAFE_BROWSING']:
@ -28,10 +32,6 @@ if CONFIG['MOZ_SAFE_BROWSING']:
EXTRA_PP_COMPONENTS += [
'AboutRedirector.js',
'BrowserCLH.js',
'BrowserStartup.js',
'components.manifest',
'DirectoryProvider.js',
'HelperAppDialog.js',
'SessionStore.js',
'Sidebar.js',
]

View File

@ -18,6 +18,7 @@
<!ENTITY appbarFindInPage2.label "Find in page">
<!ENTITY appbarViewOnDesktop2.label "View on desktop">
<!ENTITY appbarMSMetaData2.label "Get app for this site">
<!ENTITY appbarViewPageSource.label "View page source">
<!ENTITY topSitesHeader.label "Top Sites">
<!ENTITY bookmarksHeader.label "Bookmarks">

View File

@ -54,11 +54,11 @@ browserForOpenLocation=Open Location
# Download Manager
downloadsUnknownSize=Unknown size
downloadRun=Run
downloadOpen=Open
downloadSave=Save
downloadCancel=Cancel
downloadTryAgain=Try Again
downloadRunNow=Run it now
downloadOpenNow=Open it now
# LOCALIZATION NOTE (downloadShowInFiles): 'Files' refers to the Windows 8 file explorer
downloadShowInFiles=Show in Files
@ -74,8 +74,8 @@ alertDownloadsDone2=%S has been downloaded
alertTapToSave=Tap to save this file.
alertDownloadsSize=Download too big
alertDownloadsNoSpace=Not enough storage space
# LOCALIZATION NOTE (alertDownloadSave): #1 is the file name, #2 is the file size, #3 is the file host
alertDownloadSave=Do you want to run or save #1 (#2) from #3?
# LOCALIZATION NOTE (alertDownloadSave2): #1 is the file name, #2 is the file size, #3 is the file host
alertDownloadSave2=Do you want to open or save #1 (#2) from #3?
# LOCALIZATION NOTE (alertDownloadMultiple): Semi-colon list of plural forms.
# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
# #1 is the number of files, #2 is (amount downloaded so far / total amount to download) and #3 is seconds remaining

View File

@ -219,6 +219,11 @@ menulist {
width: 100%;
}
/* ensure click events are dispatched targetting the select option not the label */
#select-commands > .option-command > label {
pointer-events: none;
}
/* listcell element doesn't have flex="1" so we need to force it */
.option-command > listcell {
-moz-box-flex: 1 !important;

View File

@ -3511,23 +3511,14 @@ private void CancelNotification()
prgIntent.setPackage(sArgs[0]);
prgIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// Get the main activity for this package
try {
PackageInfo pi = pm.getPackageInfo(sArgs[0], PackageManager.GET_ACTIVITIES | PackageManager.GET_INTENT_FILTERS);
ActivityInfo [] ai = pi.activities;
for (int i = 0; i < ai.length; i++)
{
ActivityInfo a = ai[i];
if (a.name.length() > 0)
{
prgIntent.setClassName(a.packageName, a.name);
break;
}
}
}
catch (NameNotFoundException e)
{
final ComponentName c = pm.getLaunchIntentForPackage(sArgs[0]).getComponent();
prgIntent.setClassName(c.getPackageName(), c.getClassName());
} catch (Exception e) {
e.printStackTrace();
}
return "Unable to find main activity for package: " + sArgs[0];
}
if (sArgs.length > 1)
{

View File

@ -95,6 +95,7 @@ oddly_ordered_inclnames = set([
'ctypes/typedefs.h', # Included multiple times in the body of ctypes/CTypes.h
'jsautokw.h', # Included in the body of frontend/TokenStream.h
'jswin.h', # Must be #included before <psapi.h>
'machine/endian.h', # Must be included after <sys/types.h> on BSD
'winbase.h', # Must precede other system headers(?)
'windef.h' # Must precede other system headers(?)
])

View File

@ -195,9 +195,8 @@ MOZ_PSEUDO_DERECURSE :=
endif
endif
# Disable MOZ_PSEUDO_DERECURSE on the second PGO pass until it's widely
# tested.
ifdef MOZ_PROFILE_USE
# Disable MOZ_PSEUDO_DERECURSE on PGO builds until it's fixed.
ifneq (,$(MOZ_PROFILE_USE)$(MOZ_PROFILE_GENERATE))
MOZ_PSEUDO_DERECURSE :=
endif

View File

@ -1331,7 +1331,7 @@ endif
endif
endif
libs realchrome:: $(CHROME_DEPS) $(FINAL_TARGET)/chrome
libs realchrome:: $(FINAL_TARGET)/chrome
$(call py_action,jar_maker,\
$(QUIET) -j $(FINAL_TARGET)/chrome \
$(MAKE_JARS_FLAGS) $(XULPPFLAGS) $(DEFINES) $(ACDEFINES) \

View File

@ -38,6 +38,7 @@
#include "nsIDOMWindow.h"
#include "nsPIDOMWindow.h"
#include "nsDisplayList.h"
#include "nsFocusManager.h"
#include "nsTArray.h"
@ -1712,6 +1713,90 @@ CanvasRenderingContext2D::Stroke()
Redraw();
}
void CanvasRenderingContext2D::DrawSystemFocusRing(mozilla::dom::Element& aElement)
{
EnsureUserSpacePath();
if (!mPath) {
return;
}
if(DrawCustomFocusRing(aElement)) {
Save();
// set state to conforming focus state
ContextState& state = CurrentState();
state.globalAlpha = 1.0;
state.shadowBlur = 0;
state.shadowOffset.x = 0;
state.shadowOffset.y = 0;
state.op = mozilla::gfx::OP_OVER;
state.lineCap = CAP_BUTT;
state.lineJoin = mozilla::gfx::JOIN_MITER_OR_BEVEL;
state.lineWidth = 1;
CurrentState().dash.Clear();
// color and style of the rings is the same as for image maps
// set the background focus color
CurrentState().SetColorStyle(STYLE_STROKE, NS_RGBA(255, 255, 255, 255));
// draw the focus ring
Stroke();
// set dashing for foreground
FallibleTArray<mozilla::gfx::Float>& dash = CurrentState().dash;
dash.AppendElement(1);
dash.AppendElement(1);
// set the foreground focus color
CurrentState().SetColorStyle(STYLE_STROKE, NS_RGBA(0,0,0, 255));
// draw the focus ring
Stroke();
Restore();
}
}
bool CanvasRenderingContext2D::DrawCustomFocusRing(mozilla::dom::Element& aElement)
{
EnsureUserSpacePath();
HTMLCanvasElement* canvas = GetCanvas();
if (!canvas|| !nsContentUtils::ContentIsDescendantOf(&aElement, canvas)) {
return false;
}
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
// check that the element i focused
nsCOMPtr<nsIDOMElement> focusedElement;
fm->GetFocusedElement(getter_AddRefs(focusedElement));
if (SameCOMIdentity(aElement.AsDOMNode(), focusedElement)) {
// get the bounds of the current path
mgfx::Rect bounds;
bounds = mPath->GetBounds(mTarget->GetTransform());
// and set them as the accessible area
nsRect rect(canvas->ClientLeft() + bounds.x, canvas->ClientTop() + bounds.y,
bounds.width, bounds.height);
rect.x *= AppUnitsPerCSSPixel();
rect.y *= AppUnitsPerCSSPixel();
rect.width *= AppUnitsPerCSSPixel();
rect.height *= AppUnitsPerCSSPixel();
nsIFrame* frame = aElement.GetPrimaryFrame();
if(frame) {
frame->SetRect(rect);
}
return true;
}
}
return false;
}
void
CanvasRenderingContext2D::Clip(const CanvasWindingRule& winding)
{

View File

@ -171,6 +171,8 @@ public:
void BeginPath();
void Fill(const CanvasWindingRule& winding);
void Stroke();
void DrawSystemFocusRing(mozilla::dom::Element& element);
bool DrawCustomFocusRing(mozilla::dom::Element& element);
void Clip(const CanvasWindingRule& winding);
bool IsPointInPath(double x, double y, const CanvasWindingRule& winding);
bool IsPointInStroke(double x, double y);

View File

@ -890,6 +890,7 @@ protected:
// -------------------------------------------------------------------------
// WebGL extensions (implemented in WebGLContextExtensions.cpp)
enum WebGLExtensionID {
EXT_sRGB,
EXT_texture_filter_anisotropic,
OES_element_index_uint,
OES_standard_derivatives,
@ -952,7 +953,7 @@ protected:
bool ValidateGLSLVariableName(const nsAString& name, const char *info);
bool ValidateGLSLCharacter(PRUnichar c);
bool ValidateGLSLString(const nsAString& string, const char *info);
bool ValidateTexImage2DFormat(GLenum format, const char* info);
bool ValidateTexImage2DTarget(GLenum target, GLsizei width, GLsizei height, const char* info);
bool ValidateCompressedTextureSize(GLenum target, GLint level, GLenum format, GLsizei width, GLsizei height, uint32_t byteLength, const char* info);
bool ValidateLevelWidthHeightForTarget(GLenum target, GLint level, GLsizei width, GLsizei height, const char* info);

View File

@ -17,6 +17,7 @@ using namespace mozilla::gl;
// must match WebGLContext::WebGLExtensionID
static const char *sExtensionNames[] = {
"EXT_sRGB",
"EXT_texture_filter_anisotropic",
"OES_element_index_uint",
"OES_standard_derivatives",
@ -113,6 +114,8 @@ bool WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const
gl->IsExtensionSupported(GLContext::ANGLE_depth_texture);
case ANGLE_instanced_arrays:
return WebGLExtensionInstancedArrays::IsSupported(this);
case EXT_sRGB:
return WebGLExtensionSRGB::IsSupported(this);
default:
// For warnings-as-errors.
break;
@ -257,6 +260,9 @@ WebGLContext::EnableExtension(WebGLExtensionID ext)
case ANGLE_instanced_arrays:
obj = new WebGLExtensionInstancedArrays(this);
break;
case EXT_sRGB:
obj = new WebGLExtensionSRGB(this);
break;
default:
MOZ_ASSERT(false, "should not get there.");
}

View File

@ -584,7 +584,7 @@ WebGLContext::CopyTexImage2D(GLenum target,
sizeMayChange = width != imageInfo.Width() ||
height != imageInfo.Height() ||
internalformat != imageInfo.Format() ||
internalformat != imageInfo.InternalFormat() ||
type != imageInfo.Type();
}
@ -661,7 +661,7 @@ WebGLContext::CopyTexSubImage2D(GLenum target,
if (yoffset + height > texHeight || yoffset + height < 0)
return ErrorInvalidValue("copyTexSubImage2D: yoffset+height is too large");
GLenum format = imageInfo.Format();
GLenum format = imageInfo.InternalFormat();
bool texFormatRequiresAlpha = format == LOCAL_GL_RGBA ||
format == LOCAL_GL_ALPHA ||
format == LOCAL_GL_LUMINANCE_ALPHA;
@ -1045,7 +1045,7 @@ WebGLContext::BindFakeBlackTexturesHelper(
}
bool alpha = s == WebGLTextureFakeBlackStatus::UninitializedImageData &&
FormatHasAlpha(boundTexturesArray[i]->ImageInfoBase().Format());
FormatHasAlpha(boundTexturesArray[i]->ImageInfoBase().InternalFormat());
ScopedDeletePtr<FakeBlackTexture>&
blackTexturePtr = alpha
? transparentTextureScopedPtr
@ -1205,7 +1205,7 @@ WebGLContext::GenerateMipmap(GLenum target)
if (!tex->IsFirstImagePowerOfTwo())
return ErrorInvalidOperation("generateMipmap: Level zero of texture does not have power-of-two width and height.");
GLenum format = tex->ImageInfoAt(imageTarget, 0).Format();
GLenum format = tex->ImageInfoAt(imageTarget, 0).InternalFormat();
if (IsTextureFormatCompressed(format))
return ErrorInvalidOperation("generateMipmap: Texture data at level zero is compressed.");
@ -1415,6 +1415,15 @@ WebGLContext::GetFramebufferAttachmentParameter(JSContext* cx,
if (fba.Renderbuffer()) {
switch (pname) {
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
if (IsExtensionEnabled(EXT_sRGB)) {
const GLenum internalFormat = fba.Renderbuffer()->InternalFormat();
return (internalFormat == LOCAL_GL_SRGB_EXT ||
internalFormat == LOCAL_GL_SRGB_ALPHA_EXT ||
internalFormat == LOCAL_GL_SRGB8_ALPHA8_EXT) ?
JS::NumberValue(uint32_t(LOCAL_GL_SRGB_EXT)) :
JS::NumberValue(uint32_t(LOCAL_GL_LINEAR));
}
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return JS::NumberValue(uint32_t(LOCAL_GL_RENDERBUFFER));
@ -1429,6 +1438,16 @@ WebGLContext::GetFramebufferAttachmentParameter(JSContext* cx,
}
} else if (fba.Texture()) {
switch (pname) {
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
if (IsExtensionEnabled(EXT_sRGB)) {
const GLenum internalFormat =
fba.Texture()->ImageInfoBase().InternalFormat();
return (internalFormat == LOCAL_GL_SRGB_EXT ||
internalFormat == LOCAL_GL_SRGB_ALPHA_EXT) ?
JS::NumberValue(uint32_t(LOCAL_GL_SRGB_EXT)) :
JS::NumberValue(uint32_t(LOCAL_GL_LINEAR));
}
case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
return JS::NumberValue(uint32_t(LOCAL_GL_TEXTURE));
@ -2493,6 +2512,8 @@ WebGLContext::RenderbufferStorage(GLenum target, GLenum internalformat, GLsizei
// We emulate this in WebGLRenderbuffer if we don't have the requisite extension.
internalformatForGL = LOCAL_GL_DEPTH24_STENCIL8;
break;
case LOCAL_GL_SRGB8_ALPHA8_EXT:
break;
default:
return ErrorInvalidEnumInfo("renderbufferStorage: internalformat", internalformat);
}
@ -3679,7 +3700,7 @@ GLenum WebGLContext::CheckedTexImage2D(GLenum target,
const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(target, level);
sizeMayChange = width != imageInfo.Width() ||
height != imageInfo.Height() ||
format != imageInfo.Format() ||
format != imageInfo.InternalFormat() ||
type != imageInfo.Type();
}
@ -3708,18 +3729,8 @@ WebGLContext::TexImage2D_base(GLenum target, GLint level, GLenum internalformat,
return;
}
switch (format) {
case LOCAL_GL_RGB:
case LOCAL_GL_RGBA:
case LOCAL_GL_ALPHA:
case LOCAL_GL_LUMINANCE:
case LOCAL_GL_LUMINANCE_ALPHA:
case LOCAL_GL_DEPTH_COMPONENT:
case LOCAL_GL_DEPTH_STENCIL:
break;
default:
return ErrorInvalidEnumInfo("texImage2D: internal format", internalformat);
}
if (!ValidateTexImage2DFormat(format, "texImage2D: format"))
return;
if (format != internalformat)
return ErrorInvalidOperation("texImage2D: format does not match internalformat");
@ -3791,6 +3802,23 @@ WebGLContext::TexImage2D_base(GLenum target, GLint level, GLenum internalformat,
// format == internalformat, as checked above and as required by ES.
internalformat = InternalFormatForFormatAndType(format, type, gl->IsGLES2());
// Handle ES2 and GL differences when supporting sRGB internal formats. GL ES
// requires that format == internalformat, but GL will fail in this case.
// GL requires:
// format -> internalformat
// GL_RGB GL_SRGB_EXT
// GL_RGBA GL_SRGB_ALPHA_EXT
if (!gl->IsGLES2()) {
switch (internalformat) {
case LOCAL_GL_SRGB_EXT:
format = LOCAL_GL_RGB;
break;
case LOCAL_GL_SRGB_ALPHA_EXT:
format = LOCAL_GL_RGBA;
break;
}
}
GLenum error = LOCAL_GL_NO_ERROR;
WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::NoImageData;
@ -3839,7 +3867,7 @@ WebGLContext::TexImage2D_base(GLenum target, GLint level, GLenum internalformat,
// have NoImageData at this point.
MOZ_ASSERT(imageInfoStatusIfSuccess != WebGLImageDataStatus::NoImageData);
tex->SetImageInfo(target, level, width, height, format, type, imageInfoStatusIfSuccess);
tex->SetImageInfo(target, level, width, height, internalformat, type, imageInfoStatusIfSuccess);
ReattachTextureToAnyFramebufferToWorkAroundBugs(tex, level);
}
@ -3959,7 +3987,7 @@ WebGLContext::TexSubImage2D_base(GLenum target, GLint level,
return ErrorInvalidValue("texSubImage2D: subtexture rectangle out of bounds");
// Require the format and type in texSubImage2D to match that of the existing texture as created by texImage2D
if (imageInfo.Format() != format || imageInfo.Type() != type)
if (imageInfo.InternalFormat() != format || imageInfo.Type() != type)
return ErrorInvalidOperation("texSubImage2D: format or type doesn't match the existing texture");
if (imageInfo.HasUninitializedImageData()) {
@ -4157,8 +4185,10 @@ WebGLTexelFormat mozilla::GetWebGLTexelFormat(GLenum format, GLenum type)
if (type == LOCAL_GL_UNSIGNED_BYTE) {
switch (format) {
case LOCAL_GL_RGBA:
case LOCAL_GL_SRGB_ALPHA_EXT:
return WebGLTexelFormat::RGBA8;
case LOCAL_GL_RGB:
case LOCAL_GL_SRGB_EXT:
return WebGLTexelFormat::RGB8;
case LOCAL_GL_ALPHA:
return WebGLTexelFormat::A8;

View File

@ -300,6 +300,32 @@ bool WebGLContext::ValidateGLSLString(const nsAString& string, const char *info)
return true;
}
bool WebGLContext::ValidateTexImage2DFormat(GLenum format, const char* info)
{
if (IsExtensionEnabled(EXT_sRGB)) {
switch (format) {
case LOCAL_GL_SRGB_EXT:
case LOCAL_GL_SRGB_ALPHA_EXT:
return true;
}
}
switch (format) {
case LOCAL_GL_RGB:
case LOCAL_GL_RGBA:
case LOCAL_GL_ALPHA:
case LOCAL_GL_LUMINANCE:
case LOCAL_GL_LUMINANCE_ALPHA:
case LOCAL_GL_DEPTH_COMPONENT:
case LOCAL_GL_DEPTH_STENCIL:
return true;
break;
}
ErrorInvalidEnumInfo(info, format);
return false;
}
bool WebGLContext::ValidateTexImage2DTarget(GLenum target, GLsizei width, GLsizei height,
const char* info)
{
@ -464,8 +490,10 @@ uint32_t WebGLContext::GetBitsPerTexel(GLenum format, GLenum type)
case LOCAL_GL_LUMINANCE_ALPHA:
return 2 * multiplier;
case LOCAL_GL_RGB:
case LOCAL_GL_SRGB_EXT:
return 3 * multiplier;
case LOCAL_GL_RGBA:
case LOCAL_GL_SRGB_ALPHA_EXT:
return 4 * multiplier;
case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
@ -562,9 +590,11 @@ bool WebGLContext::ValidateTexFormatAndType(GLenum format, GLenum type, int jsAr
*texelSize = 2 * texMultiplier;
return true;
case LOCAL_GL_RGB:
case LOCAL_GL_SRGB_EXT:
*texelSize = 3 * texMultiplier;
return true;
case LOCAL_GL_RGBA:
case LOCAL_GL_SRGB_ALPHA_EXT:
*texelSize = 4 * texMultiplier;
return true;
default:

View File

@ -0,0 +1,40 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "WebGLContext.h"
#include "WebGLExtensions.h"
#include "mozilla/dom/WebGLRenderingContextBinding.h"
#include "GLContext.h"
using namespace mozilla;
WebGLExtensionSRGB::WebGLExtensionSRGB(WebGLContext* context)
: WebGLExtensionBase(context)
{
MOZ_ASSERT(IsSupported(context), "should not construct WebGLExtensionSRGB: "
"sRGB is unsupported.");
gl::GLContext* gl = context->GL();
if (!gl->IsGLES()) {
// Desktop OpenGL requires the following to be enabled to support
// sRGB operations on framebuffers
gl->MakeCurrent();
gl->fEnable(LOCAL_GL_FRAMEBUFFER_SRGB_EXT);
}
}
WebGLExtensionSRGB::~WebGLExtensionSRGB()
{
}
bool
WebGLExtensionSRGB::IsSupported(const WebGLContext* context)
{
gl::GLContext* gl = context->GL();
return gl->IsSupported(gl::GLFeature::sRGB);
}
IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionSRGB)

View File

@ -109,6 +109,18 @@ public:
DECL_WEBGL_EXTENSION_GOOP
};
class WebGLExtensionSRGB
: public WebGLExtensionBase
{
public:
WebGLExtensionSRGB(WebGLContext*);
virtual ~WebGLExtensionSRGB();
static bool IsSupported(const WebGLContext* context);
DECL_WEBGL_EXTENSION_GOOP
};
class WebGLExtensionStandardDerivatives
: public WebGLExtensionBase
{

View File

@ -48,7 +48,7 @@ bool
WebGLFramebuffer::Attachment::HasAlpha() const {
GLenum format = 0;
if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
format = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).Format();
format = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).InternalFormat();
else if (Renderbuffer())
format = Renderbuffer()->InternalFormat();
return FormatHasAlpha(format);
@ -105,6 +105,26 @@ WebGLFramebuffer::Attachment::HasSameDimensionsAs(const Attachment& other) const
thisRect->HasSameDimensionsAs(*otherRect);
}
static inline bool
IsValidAttachedTextureColorFormat(GLenum format) {
return (
/* linear 8-bit formats */
format == LOCAL_GL_ALPHA ||
format == LOCAL_GL_LUMINANCE ||
format == LOCAL_GL_LUMINANCE_ALPHA ||
format == LOCAL_GL_RGB ||
format == LOCAL_GL_RGBA ||
/* sRGB 8-bit formats */
format == LOCAL_GL_SRGB_EXT ||
format == LOCAL_GL_SRGB_ALPHA_EXT ||
/* linear float32 formats */
format == LOCAL_GL_ALPHA32F_ARB ||
format == LOCAL_GL_LUMINANCE32F_ARB ||
format == LOCAL_GL_LUMINANCE_ALPHA32F_ARB ||
format == LOCAL_GL_RGB32F_ARB ||
format == LOCAL_GL_RGBA32F_ARB);
}
bool
WebGLFramebuffer::Attachment::IsComplete() const {
const WebGLRectangleObject *thisRect = RectangleObject();
@ -118,7 +138,7 @@ WebGLFramebuffer::Attachment::IsComplete() const {
if (!mTexturePtr->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
return false;
GLenum format = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).Format();
GLenum format = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).InternalFormat();
if (mAttachmentPoint == LOCAL_GL_DEPTH_ATTACHMENT) {
return format == LOCAL_GL_DEPTH_COMPONENT;
@ -128,11 +148,7 @@ WebGLFramebuffer::Attachment::IsComplete() const {
}
else if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
mAttachmentPoint < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + WebGLContext::sMaxColorAttachments)) {
return (format == LOCAL_GL_ALPHA ||
format == LOCAL_GL_LUMINANCE ||
format == LOCAL_GL_LUMINANCE_ALPHA ||
format == LOCAL_GL_RGB ||
format == LOCAL_GL_RGBA);
return IsValidAttachedTextureColorFormat(format);
}
MOZ_CRASH("Invalid WebGL attachment poin?");
}
@ -153,7 +169,8 @@ WebGLFramebuffer::Attachment::IsComplete() const {
mAttachmentPoint < GLenum(LOCAL_GL_COLOR_ATTACHMENT0 + WebGLContext::sMaxColorAttachments)) {
return (format == LOCAL_GL_RGB565 ||
format == LOCAL_GL_RGB5_A1 ||
format == LOCAL_GL_RGBA4);
format == LOCAL_GL_RGBA4 ||
format == LOCAL_GL_SRGB8_ALPHA8_EXT);
}
MOZ_CRASH("Invalid WebGL attachment poin?");
}

View File

@ -107,6 +107,7 @@ WebGLRenderbuffer::MemoryUsage() const {
primarySize = 3*pixels;
break;
case LOCAL_GL_RGBA8:
case LOCAL_GL_SRGB8_ALPHA8_EXT:
case LOCAL_GL_DEPTH24_STENCIL8:
case LOCAL_GL_DEPTH_COMPONENT32:
primarySize = 4*pixels;

View File

@ -48,7 +48,7 @@ int64_t
WebGLTexture::ImageInfo::MemoryUsage() const {
if (mImageDataStatus == WebGLImageDataStatus::NoImageData)
return 0;
int64_t texelSizeInBits = WebGLContext::GetBitsPerTexel(mFormat, mType);
int64_t texelSizeInBits = WebGLContext::GetBitsPerTexel(mInternalFormat, mType);
return int64_t(mWidth) * int64_t(mHeight) * texelSizeInBits / 8;
}
@ -418,7 +418,7 @@ WebGLTexture::DoDeferredImageInitialization(GLenum imageTarget, GLint level)
mContext->MakeContextCurrent();
gl::ScopedBindTexture autoBindTex(mContext->gl, GLName(), mTarget);
WebGLTexelFormat texelformat = GetWebGLTexelFormat(imageInfo.mFormat, imageInfo.mType);
WebGLTexelFormat texelformat = GetWebGLTexelFormat(imageInfo.mInternalFormat, imageInfo.mType);
uint32_t texelsize = WebGLTexelConversions::TexelBytesForFormat(texelformat);
CheckedUint32 checked_byteLength
= WebGLContext::GetImageSize(
@ -430,9 +430,9 @@ WebGLTexture::DoDeferredImageInitialization(GLenum imageTarget, GLint level)
void *zeros = calloc(1, checked_byteLength.value());
mContext->UpdateWebGLErrorAndClearGLError();
mContext->gl->fTexImage2D(imageTarget, level, imageInfo.mFormat,
mContext->gl->fTexImage2D(imageTarget, level, imageInfo.mInternalFormat,
imageInfo.mWidth, imageInfo.mHeight,
0, imageInfo.mFormat, imageInfo.mType,
0, imageInfo.mInternalFormat, imageInfo.mType,
zeros);
GLenum error = LOCAL_GL_NO_ERROR;
mContext->UpdateWebGLErrorAndClearGLError(&error);

View File

@ -80,7 +80,7 @@ public:
{
public:
ImageInfo()
: mFormat(0)
: mInternalFormat(0)
, mType(0)
, mImageDataStatus(WebGLImageDataStatus::NoImageData)
{}
@ -88,7 +88,7 @@ public:
ImageInfo(GLsizei width, GLsizei height,
GLenum format, GLenum type, WebGLImageDataStatus status)
: WebGLRectangleObject(width, height)
, mFormat(format)
, mInternalFormat(format)
, mType(type)
, mImageDataStatus(status)
{
@ -100,7 +100,7 @@ public:
return mImageDataStatus == a.mImageDataStatus &&
mWidth == a.mWidth &&
mHeight == a.mHeight &&
mFormat == a.mFormat &&
mInternalFormat == a.mInternalFormat &&
mType == a.mType;
}
bool operator!=(const ImageInfo& a) const {
@ -120,10 +120,10 @@ public:
return mImageDataStatus == WebGLImageDataStatus::UninitializedImageData;
}
int64_t MemoryUsage() const;
GLenum Format() const { return mFormat; }
GLenum InternalFormat() const { return mInternalFormat; }
GLenum Type() const { return mType; }
protected:
GLenum mFormat, mType;
GLenum mInternalFormat, mType;
WebGLImageDataStatus mImageDataStatus;
friend class WebGLTexture;

View File

@ -55,6 +55,7 @@ if CONFIG['MOZ_WEBGL']:
'WebGLExtensionElementIndexUint.cpp',
'WebGLExtensionInstancedArrays.cpp',
'WebGLExtensionLoseContext.cpp',
'WebGLExtensionSRGB.cpp',
'WebGLExtensionStandardDerivatives.cpp',
'WebGLExtensionTextureFilterAnisotropic.cpp',
'WebGLExtensionTextureFloat.cpp',

View File

@ -91,6 +91,7 @@ support-files =
[test_bug866575.html]
[test_bug902651.html]
[test_canvas.html]
[test_canvas_focusring.html]
[test_canvas_font_setter.html]
[test_canvas_strokeStyle_getter.html]
[test_drawImageIncomplete.html]

View File

@ -0,0 +1,18 @@
<!--docytpe html-->
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<script>
window.onload=function(){
var c=document.getElementById("myCanvas").getContext("2d");
c.beginPath();
c.strokeRect(10, 10, 200, 200);
}
</script>
</head>
<body>
<canvas id="myCanvas" height="500" width="500" style="border:1px solid black">
</canvas>
</body></html>

View File

@ -0,0 +1,32 @@
<!--docytpe html-->
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<script>
window.onload=function(){
var c=document.getElementById("myCanvas").getContext("2d");
var in1=document.getElementById("in1");
var in2=document.getElementById("in2");
in1.onfocus=function(){
c.beginPath();
c.rect(10, 10, 200, 200);
if(c.drawCustomFocusRing(in1)) {
c.stroke();
}
c.beginPath();
c.rect(10, 220, 200, 200);
if(c.drawCustomFocusRing(in2)) {
c.stroke();
}
}
in1.focus();
}
</script>
</head>
<body>
<canvas id="myCanvas" height="500" width="500" style="border:1px solid black">
<input id="in1" type="range" min="1" max="12">
<input id="in2" type="range" min="1" max="12">
</canvas>
</body></html>

View File

@ -0,0 +1,18 @@
<!--docytpe html-->
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<script>
window.onload=function(){
var c=document.getElementById("myCanvas").getContext("2d");
c.beginPath();
c.mozDash = [1,1];
c.strokeRect(10, 10, 200, 200);
}
</script>
</head>
<body>
<canvas id="myCanvas" height="500" width="500" style="border:1px solid black">
</canvas>
</body></html>

View File

@ -0,0 +1,28 @@
<!--docytpe html-->
<html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta charset="UTF-8">
<script>
window.onload=function(){
var c=document.getElementById("myCanvas").getContext("2d");
var in1=document.getElementById("in1");
var in2=document.getElementById("in2");
in1.onfocus=function(){
c.beginPath();
c.rect(10, 10, 200, 200);
c.drawSystemFocusRing(in1);
c.beginPath();
c.rect(10, 220, 200, 200);
c.drawSystemFocusRing(in2);
}
in1.focus();
}
</script>
</head>
<body>
<canvas id="myCanvas" height="500" width="500" style="border:1px solid black">
<input id="in1" type="range" min="1" max="12">
<input id="in2" type="range" min="1" max="12">
</canvas>
</body></html>

View File

@ -188,3 +188,7 @@ skip-if(!winWidget) pref(webgl.prefer-native-gl,true) pref(webgl.prefer-16bpp,tr
# Bug 815648
== stroketext-shadow.html stroketext-shadow-ref.html
# focus rings
pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawSystemFocusRing.html drawSystemFocusRing-ref.html
pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html

View File

@ -0,0 +1,98 @@
<!DOCTYPE HTML>
<title>Canvas Tests</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="/tests/SimpleTest/test.css">
<body>
<script>
SimpleTest.waitForExplicitFinish();
const Cc = SpecialPowers.Cc;
const Cr = SpecialPowers.Cr;
SpecialPowers.setBoolPref("canvas.focusring.enabled", true);
</script>
<p>Canvas test: drawCustomFocusRing</p>
<canvas id="c688" class="output" width="100" height="50">+
<input id="button1" type="range" min="1" max="12"></input>
<input id="button2" type="range" min="1" max="12"></input>
</canvas>
<script type="text/javascript">
function test_drawCustomFocusRing_canvas() {
var c = document.getElementById("c688");
var ctx = c.getContext("2d");
ctx.beginPath();
var b1 = document.getElementById('button1');
var b2 = document.getElementById('button2');
ok(!ctx.drawCustomFocusRing(b1), "button 1 is focused");
ok(!ctx.drawCustomFocusRing(b2), "button 2 is focused");
b1.focus();
ok(ctx.drawCustomFocusRing(b1), "button 1 should not be focused");
}
</script>
<p>Canvas test: drawSystemFocusRing</p>
<canvas id="c689" class="output" width="50" height="25">
<input id="button3" type="range" min="1" max="12"></input>
<input id="button4" type="range" min="1" max="12"></input>
</canvas>
<script type="text/javascript">
function isEmptyCanvas(ctx, w, h) {
var imgdata = ctx.getImageData(0, 0, w, h);
for(var x = 0; x < w*h*4; x++)
if(imgdata.data[x] != 0)
return false;
return true;
}
function test_drawSystemFocusRing_canvas() {
var c = document.getElementById("c689");
var ctx = c.getContext("2d");
var b1 = document.getElementById('button3');
var b2 = document.getElementById('button4');
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.rect(10, 10, 30, 30);
ctx.drawSystemFocusRing(b1);
ok(isEmptyCanvas(ctx, ctx.canvas.width, ctx.canvas.height), "focus of button 1 is drawn");
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.rect(50, 10, 30, 30);
ctx.drawSystemFocusRing(b2);
ctx.rect(50, 10, 30, 30);
ctx.drawSystemFocusRing(b2);
ok(isEmptyCanvas(ctx, ctx.canvas.width, ctx.canvas.height), "focus of button 2 is drawn");
b1.focus();
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.beginPath();
ctx.rect(10, 10, 30, 30);
ctx.drawSystemFocusRing(b1);
ok(!isEmptyCanvas(ctx, ctx.canvas.width, ctx.canvas.height) , "focus of button 1 is not drawn");
}
</script>
<script>
function runTests() {
try {
test_drawCustomFocusRing_canvas();
} catch(e) {
throw e;
ok(false, "unexpected exception thrown in: test_drawCustomFocusRing_canvas");
}
try {
test_drawSystemFocusRing_canvas();
} catch(e) {
throw e;
ok(false, "unexpected exception thrown in: test_drawSystemFocusRing_canvas");
}
SpecialPowers.setBoolPref("canvas.focusring.enabled", false);
SimpleTest.finish();
}
addLoadEvent(runTests);
</script>

View File

@ -6,4 +6,4 @@ webgl-debug-renderer-info.html
webgl-debug-shaders.html
--min-version 1.0.2 webgl-compressed-texture-s3tc.html
--min-version 1.0.2 webgl-depth-texture.html
ext-sRGB.html

View File

@ -0,0 +1,363 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<link rel="stylesheet" href="../../resources/js-test-style.css"/>
<script src="../../resources/js-test-pre.js"></script>
<script src="../resources/webgl-test.js"></script>
<script src="../resources/webgl-test-utils.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<canvas id="canvas" width="16" height="16" style="width: 50px; height: 50px; border: 1px solid black;"></canvas>
<!-- Shaders to test output -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 aPosition;
void main() {
gl_Position = aPosition;
}
</script>
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform float uColor;
void main() {
gl_FragColor = vec4(uColor, uColor, uColor, 1);
}
</script>
<script>
"use strict";
var wtu = WebGLTestUtils;
var canvas;
var gl;
var ext = null;
function getExtension() {
ext = gl.getExtension("EXT_sRGB");
}
function listsExtension() {
var supported = gl.getSupportedExtensions();
return (supported.indexOf("EXT_sRGB") >= 0);
}
function readLocation(x, y) {
var pixel = new Uint8Array(1 * 1 * 4);
var px = Math.floor(x * canvas.drawingBufferWidth);
var py = Math.floor(y * canvas.drawingBufferHeight);
gl.readPixels(px, py, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
return pixel;
}
function toVec3String(val) {
if (typeof(val) == 'number') {
return toVec3String([val, val, val]);
}
return '[' + val[0] + ', ' + val[1] + ', ' + val[2] + ']';
}
var e = 2; // Amount of variance to allow in result pixels - may need to be tweaked higher
function expectResult(target, successMessage, failureMessage) {
var anyDiffer = false;
var source = readLocation(0.5, 0.5);
for (var m = 0; m < 3; m++) {
if (Math.abs(source[m] - target) > e) {
anyDiffer = true;
testFailed(failureMessage + "; should be " + toVec3String(target) + ", was " + toVec3String(source));
break;
}
}
if (!anyDiffer) {
testPassed(successMessage);
}
}
function createGreysRGBTexture(gl, color) {
var numPixels = gl.drawingBufferWidth * gl.drawingBufferHeight;
var size = numPixels * 3;
var buf = new Uint8Array(size);
for (var ii = 0; ii < numPixels; ++ii) {
var off = ii * 3;
buf[off + 0] = color;
buf[off + 1] = color;
buf[off + 2] = color;
}
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D,
0,
ext.SRGB_EXT,
gl.drawingBufferWidth,
gl.drawingBufferHeight,
0,
ext.SRGB_EXT,
gl.UNSIGNED_BYTE,
buf);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
return tex;
}
function testValidFormat(fn, internalFormat, formatName) {
fn(internalFormat);
glErrorShouldBe(gl, gl.NO_ERROR, "was able to create type " + formatName);
}
function testInvalidFormat(fn, internalFormat, formatName) {
fn(internalFormat);
var err = gl.getError();
if (err == gl.NO_ERROR) {
testFailed("should NOT be able to create type " + formatName);
} else if (err == gl.INVALID_OPERATION) {
testFailed("should return gl.INVALID_ENUM for type " + formatName);
} else if (err == gl.INVALID_ENUM) {
testPassed("not able to create invalid format: " + formatName);
}
}
var textureFormatFixture = {
desc: "Checking texture formats",
create: function(format) {
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D,
0, // level
format, // internalFormat
gl.drawingBufferWidth, // width
gl.drawingBufferHeight, // height
0, // border
format, // format
gl.UNSIGNED_BYTE, // type
null); // data
},
tests: [
{
desc: "Checking valid formats",
fn: testValidFormat,
formats: [ 'SRGB_EXT', 'SRGB_ALPHA_EXT' ]
},
{
desc: "Checking invalid formats",
fn: testInvalidFormat,
formats: [ 'SRGB8_ALPHA8_EXT' ]
}
]
};
var renderbufferFormatFixture = {
desc: "Checking renderbuffer formats",
create: function(format) {
var rbo = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
gl.renderbufferStorage(gl.RENDERBUFFER,
format,
gl.drawingBufferWidth,
gl.drawingBufferHeight);
},
tests: [
{
desc: "Checking valid formats",
fn: testValidFormat,
formats: [ 'SRGB8_ALPHA8_EXT' ]
},
{
desc: "Checking invalid formats",
fn: testInvalidFormat,
formats: [ 'SRGB_EXT', 'SRGB_ALPHA_EXT' ]
}
]
};
description("Test sRGB texture support");
debug("");
debug("Canvas.getContext");
canvas = document.getElementById("canvas");
gl = wtu.create3DContext(canvas);
if (!gl) {
testFailed("context does not exist");
} else {
testPassed("context exists");
debug("");
debug("Checking sRGB texture support");
// Query the extension and store globally so shouldBe can access it
ext = gl.getExtension("EXT_sRGB");
if (!ext) {
testPassed("No EXT_sRGB support -- this is legal");
runSupportedTest(false);
} else {
testPassed("Successfully enabled EXT_sRGB extension");
runSupportedTest(true);
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
runFormatTest(textureFormatFixture);
runFormatTest(renderbufferFormatFixture);
runTextureReadConversionTest();
runFramebufferTextureConversionTest();
runFramebufferRenderbufferConversionTest();
}
}
function runSupportedTest(extensionEnabled) {
if (listsExtension()) {
if (extensionEnabled) {
testPassed("EXT_sRGB listed as supported and getExtension succeeded");
} else {
testFailed("EXT_sRGB listed as supported but getExtension failed");
}
} else {
if (extensionEnabled) {
testFailed("EXT_sRGB not listed as supported but getExtension succeeded");
} else {
testPassed("EXT_sRGB not listed as supported and getExtension failed -- this is legal");
}
}
}
function runFormatTest(fixture) {
debug("");
debug(fixture.desc);
for (var tt = 0; tt < fixture.tests.length; ++tt) {
var test = fixture.tests[tt];
debug(test.desc);
for (var ii = 0; ii < test.formats.length; ++ii) {
var formatName = test.formats[ii];
test.fn(fixture.create, ext[formatName], "ext." + formatName);
}
if (tt != fixture.tests.length - 1)
debug("");
}
}
function runTextureReadConversionTest() {
debug("");
debug("Test the conversion of colors from sRGB to linear on texture read");
// Draw
var conversions = [
[ 0, 0 ],
[ 63, 13 ],
[ 127, 54 ],
[ 191, 133 ],
[ 255, 255 ]
];
var program = wtu.setupTexturedQuad(gl);
gl.uniform1i(gl.getUniformLocation(program, "tex2d"), 0);
for (var ii = 0; ii < conversions.length; ii++) {
var tex = createGreysRGBTexture(gl, conversions[ii][0]);
wtu.drawQuad(gl);
expectResult(conversions[ii][1],
"sRGB texture read returned correct data",
"sRGB texture read returned incorrect data");
}
}
function runFramebufferTextureConversionTest() {
debug("");
debug("Test the conversion of colors from linear to sRGB on framebuffer (texture) write");
var program = wtu.setupProgram(gl, ['vertexShader', 'fragmentShader'], ['aPosition'], [0]);
var tex = createGreysRGBTexture(gl, 0);
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
glErrorShouldBe(gl, gl.NO_ERROR);
shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT)', 'ext.SRGB_EXT');
shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
// Draw
var conversions = [
[ 0, 0 ],
[ 13, 63 ],
[ 54, 127 ],
[ 133, 191 ],
[ 255, 255 ]
];
wtu.setupUnitQuad(gl, 0);
for (var ii = 0; ii < conversions.length; ii++) {
gl.uniform1f(gl.getUniformLocation(program, "uColor"), conversions[ii][0]/255.0);
wtu.drawQuad(gl, [0, 0, 0, 0]);
expectResult(conversions[ii][1],
"framebuffer (texture) read returned correct data",
"framebuffer (texture) read returned incorrect data");
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
function runFramebufferRenderbufferConversionTest() {
debug("");
debug("Test the conversion of colors from linear to sRGB on framebuffer (renderbuffer) write");
function createsRGBFramebuffer(gl, width, height) {
var rbo = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
gl.renderbufferStorage(gl.RENDERBUFFER, ext.SRGB8_ALPHA8_EXT, width, height);
glErrorShouldBe(gl, gl.NO_ERROR);
var fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.RENDERBUFFER, rbo);
glErrorShouldBe(gl, gl.NO_ERROR);
shouldBe('gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT)', 'ext.SRGB_EXT');
shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
return fbo;
}
// Draw
var conversions = [
[ 0, 0 ],
[ 13, 63 ],
[ 54, 127 ],
[ 133, 191 ],
[ 255, 255 ]
];
var program = wtu.setupProgram(gl, ['vertexShader', 'fragmentShader'], ['aPosition'], [0]);
wtu.setupUnitQuad(gl, 0);
var fbo = createsRGBFramebuffer(gl, 4, 4);
for (var ii = 0; ii < conversions.length; ii++) {
gl.uniform1f(gl.getUniformLocation(program, "uColor"), conversions[ii][0]/255.0);
wtu.drawQuad(gl, [0, 0, 0, 0]);
expectResult(conversions[ii][1],
"framebuffer (renderbuffer) read returned the correct data",
"framebuffer (renderbuffer) read returned incorrect data");
}
}
debug("");
var successfullyParsed = true;
</script>
<script>finishTest();</script>
</body>
</html>

View File

@ -61,8 +61,10 @@ NS_IMPL_RELEASE_INHERITED(MediaRecorder, nsDOMEventTargetHelper)
* 2) A Session is destroyed in DestroyRunnable after MediaRecorder::Stop being called
* _and_ all encoded media data been passed to OnDataAvailable handler.
*/
class MediaRecorder::Session
class MediaRecorder::Session: public nsIObserver
{
NS_DECL_THREADSAFE_ISUPPORTS
// Main thread task.
// Create a blob event and send back to client.
class PushBlobRunnable : public nsRunnable
@ -114,7 +116,7 @@ class MediaRecorder::Session
class DestroyRunnable : public nsRunnable
{
public:
DestroyRunnable(Session *aSession)
DestroyRunnable(const already_AddRefed<Session> &aSession)
: mSession(aSession) {}
NS_IMETHODIMP Run()
@ -122,12 +124,16 @@ class MediaRecorder::Session
MOZ_ASSERT(NS_IsMainThread() && mSession.get());
MediaRecorder *recorder = mSession->mRecorder;
// If MediaRecoder is not in Inactive mode, call MediaRecoder::Stop
// and dispatch DestroyRunnable again.
// SourceMediaStream is ended, and send out TRACK_EVENT_END notification.
// Read Thread will be terminate soon.
// We need to switch MediaRecorder to "Stop" state first to make sure
// MediaRecorder is not associated with this Session anymore, then, it's
// safe to delete this Session.
if (recorder->mState != RecordingState::Inactive) {
ErrorResult result;
recorder->Stop(result);
NS_DispatchToMainThread(new DestroyRunnable(mSession.forget()));
return NS_OK;
}
@ -135,14 +141,12 @@ class MediaRecorder::Session
recorder->DispatchSimpleEvent(NS_LITERAL_STRING("stop"));
recorder->SetMimeType(NS_LITERAL_STRING(""));
// Delete session object.
mSession = nullptr;
return NS_OK;
}
private:
nsAutoPtr<Session> mSession;
// Call mSession::Release automatically while DestroyRunnable be destroy.
nsRefPtr<Session> mSession;
};
friend class PushBlobRunnable;
@ -156,21 +160,16 @@ public:
{
MOZ_ASSERT(NS_IsMainThread());
AddRef();
mEncodedBufferCache = new EncodedBufferCache(MAX_ALLOW_MEMORY_BUFFER);
}
// Only DestroyRunnable is allowed to delete Session object.
~Session()
virtual ~Session()
{
MOZ_ASSERT(NS_IsMainThread());
if (mInputPort.get()) {
mInputPort->Destroy();
}
if (mTrackUnionStream.get()) {
mTrackUnionStream->Destroy();
}
CleanupStreams();
}
void Start()
@ -183,17 +182,16 @@ public:
if (!mReadThread) {
nsresult rv = NS_NewNamedThread("Media Encoder", getter_AddRefs(mReadThread));
if (NS_FAILED(rv)) {
if (mInputPort.get()) {
mInputPort->Destroy();
}
if (mTrackUnionStream.get()) {
mTrackUnionStream->Destroy();
}
CleanupStreams();
mRecorder->NotifyError(rv);
return;
}
}
// In case source media stream does not notify track end, recieve
// shutdown notification and stop Read Thread.
nsContentUtils::RegisterShutdownObserver(this);
mReadThread->Dispatch(new ExtractRunnable(this), NS_DISPATCH_NORMAL);
}
@ -201,18 +199,8 @@ public:
{
MOZ_ASSERT(NS_IsMainThread());
// Shutdown mEncoder to stop Session::Extract
if (mInputPort.get())
{
mInputPort->Destroy();
mInputPort = nullptr;
}
if (mTrackUnionStream.get())
{
mTrackUnionStream->Destroy();
mTrackUnionStream = nullptr;
}
CleanupStreams();
nsContentUtils::UnregisterShutdownObserver(this);
}
void Pause()
@ -233,6 +221,7 @@ public:
{
nsString mimeType;
mRecorder->GetMimeType(mimeType);
return mEncodedBufferCache->ExtractBlob(mimeType);
}
@ -273,7 +262,7 @@ private:
NS_DispatchToMainThread(new PushBlobRunnable(this));
// Destroy this session object in main thread.
NS_DispatchToMainThread(new DestroyRunnable(this));
NS_DispatchToMainThread(new DestroyRunnable(already_AddRefed<Session>(this)));
}
// Bind media source with MediaEncoder to receive raw media data.
@ -281,15 +270,17 @@ private:
{
MOZ_ASSERT(NS_IsMainThread());
// Create a Track Union Stream
MediaStreamGraph* gm = mRecorder->mStream->GetStream()->Graph();
mTrackUnionStream = gm->CreateTrackUnionStream(nullptr);
MOZ_ASSERT(mTrackUnionStream, "CreateTrackUnionStream failed");
mTrackUnionStream->SetAutofinish(true);
// Bind this Track Union Stream with Source Media
mInputPort = mTrackUnionStream->AllocateInputPort(mRecorder->mStream->GetStream(), MediaInputPort::FLAG_BLOCK_OUTPUT);
// Allocate encoder and bind with union stream.
// Allocate encoder and bind with the Track Union Stream.
mEncoder = MediaEncoder::CreateEncoder(NS_LITERAL_STRING(""));
MOZ_ASSERT(mEncoder, "CreateEncoder failed");
@ -298,11 +289,37 @@ private:
}
}
void CleanupStreams()
{
if (mInputPort.get()) {
mInputPort->Destroy();
mInputPort = nullptr;
}
if (mTrackUnionStream.get()) {
mTrackUnionStream->Destroy();
mTrackUnionStream = nullptr;
}
}
NS_IMETHODIMP Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
{
MOZ_ASSERT(NS_IsMainThread());
if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
// Force stop Session to terminate Read Thread.
Stop();
}
return NS_OK;
}
private:
// Hold a reference to MediaRecoder to make sure MediaRecoder be
// destroyed after all session object dead.
nsRefPtr<MediaRecorder> mRecorder;
// Receive track data from source and dispatch to Encoder.
// Pause/ Resume controller.
nsRefPtr<ProcessedMediaStream> mTrackUnionStream;
nsRefPtr<MediaInputPort> mInputPort;
@ -320,6 +337,8 @@ private:
const int32_t mTimeSlice;
};
NS_IMPL_ISUPPORTS1(MediaRecorder::Session, nsIObserver)
MediaRecorder::~MediaRecorder()
{
MOZ_ASSERT(mSession == nullptr);

View File

@ -142,7 +142,8 @@ public:
// mLeftOverData != INT_MIN means that the panning model was HRTF and a
// tail-time reference was added. Even if the model is now equalpower,
// the reference will need to be removed.
if (mLeftOverData > 0) {
if (mLeftOverData > 0 &&
mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
} else {
if (mLeftOverData != INT_MIN) {

View File

@ -2154,16 +2154,17 @@ this.DOMApplicationRegistry = {
queuedDownload: {},
queuedPackageDownload: {},
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
aDontNeedNetwork) {
// If we are offline, register to run when we'll be online.
if (Services.io.offline) {
if ((Services.io.offline) && !aDontNeedNetwork) {
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;
@ -2368,12 +2369,13 @@ this.DOMApplicationRegistry = {
aInstallSuccessCallback(app.manifest);
}
}
let dontNeedNetwork = false;
if (manifest.package_path) {
// If it is a local app then it must been installed from a local file
// instead of web.
let origPath = jsonManifest.package_path;
if (aData.app.localInstallPath) {
dontNeedNetwork = true;
jsonManifest.package_path = "file://" + aData.app.localInstallPath;
}
// origin for install apps is meaningless here, since it's app:// and this
@ -2390,7 +2392,7 @@ this.DOMApplicationRegistry = {
if (aData.forceSuccessAck) {
// If it's a local install, there's no content process so just
// ack the install.
this.onInstallSuccessAck(app.manifestURL);
this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork);
}
},

View File

@ -3762,7 +3762,7 @@ nsGlobalWindow::GetScriptableContent(JSContext* aCx, JS::Value* aVal)
ErrorResult rv;
JS::Rooted<JSObject*> content(aCx, GetContent(aCx, rv));
if (!rv.Failed()) {
*aVal = JS::ObjectValue(*content);
*aVal = JS::ObjectOrNullValue(content);
}
return rv.ErrorCode();

View File

@ -1310,6 +1310,11 @@ DOMInterfaces = {
'headerFile': 'WebGLExtensions.h'
},
'WebGLExtensionSRGB': {
'nativeType': 'mozilla::WebGLExtensionSRGB',
'headerFile': 'WebGLExtensions.h'
},
'WebGLExtensionStandardDerivatives': {
'nativeType': 'mozilla::WebGLExtensionStandardDerivatives',
'headerFile': 'WebGLExtensions.h'

View File

@ -14,6 +14,10 @@
#include "mozilla/dom/PFMRadioChild.h"
#include "mozilla/dom/FMRadioService.h"
#include "DOMRequest.h"
#include "nsDOMClassInfo.h"
#include "nsIDocShell.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIAudioManager.h"
#undef LOG
#define LOG(args...) FM_LOG("FMRadio", args)
@ -110,6 +114,27 @@ FMRadio::Init(nsPIDOMWindow *aWindow)
mHeadphoneState = GetCurrentSwitchState(SWITCH_HEADPHONES);
RegisterSwitchObserver(SWITCH_HEADPHONES, this);
}
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
NS_ENSURE_TRUE_VOID(target);
target->AddSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
/* useCapture = */ true,
/* wantsUntrusted = */ false);
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
if (!mAudioChannelAgent) {
return;
}
mAudioChannelAgent->InitWithWeakCallback(nsIAudioChannelAgent::AUDIO_AGENT_CHANNEL_CONTENT,
this);
nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
if (docshell) {
bool isActive = false;
docshell->GetIsActive(&isActive);
mAudioChannelAgent->SetVisibilityState(isActive);
}
}
void
@ -121,6 +146,11 @@ FMRadio::Shutdown()
UnregisterSwitchObserver(SWITCH_HEADPHONES, this);
}
nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
NS_ENSURE_TRUE_VOID(target);
target->RemoveSystemEventListener(NS_LITERAL_STRING("visibilitychange"), this,
/* useCapture = */ true);
mIsShutdown = true;
}
@ -151,8 +181,14 @@ FMRadio::Notify(const FMRadioEventType& aType)
break;
case EnabledChanged:
if (Enabled()) {
int32_t playingState = 0;
mAudioChannelAgent->StartPlaying(&playingState);
SetCanPlay(playingState == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
DispatchTrustedEvent(NS_LITERAL_STRING("enabled"));
} else {
mAudioChannelAgent->StopPlaying();
DispatchTrustedEvent(NS_LITERAL_STRING("disabled"));
}
break;
@ -284,8 +320,43 @@ FMRadio::CancelSeek()
return r.forget();
}
NS_IMETHODIMP
FMRadio::HandleEvent(nsIDOMEvent* aEvent)
{
nsAutoString type;
aEvent->GetType(type);
if (!type.EqualsLiteral("visibilitychange")) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDocShell> docshell = do_GetInterface(GetOwner());
NS_ENSURE_TRUE(docshell, NS_ERROR_FAILURE);
bool isActive = false;
docshell->GetIsActive(&isActive);
mAudioChannelAgent->SetVisibilityState(isActive);
return NS_OK;
}
NS_IMETHODIMP
FMRadio::CanPlayChanged(int32_t aCanPlay)
{
SetCanPlay(aCanPlay == AudioChannelState::AUDIO_CHANNEL_STATE_NORMAL);
return NS_OK;
}
void
FMRadio::SetCanPlay(bool aCanPlay)
{
IFMRadioService::Singleton()->EnableAudio(aCanPlay);
}
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
NS_IMPL_ADDREF_INHERITED(FMRadio, nsDOMEventTargetHelper)

View File

@ -11,6 +11,7 @@
#include "nsCycleCollectionParticipant.h"
#include "mozilla/HalTypes.h"
#include "nsWeakReference.h"
#include "AudioChannelAgent.h"
class nsPIDOMWindow;
class nsIScriptContext;
@ -23,6 +24,9 @@ class FMRadio MOZ_FINAL : public nsDOMEventTargetHelper
, public hal::SwitchObserver
, public FMRadioEventObserver
, public nsSupportsWeakReference
, public nsIAudioChannelAgentCallback
, public nsIDOMEventListener
{
friend class FMRadioRequest;
@ -30,6 +34,7 @@ public:
FMRadio();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
@ -78,12 +83,19 @@ public:
IMPL_EVENT_HANDLER(antennaavailablechange);
IMPL_EVENT_HANDLER(frequencychange);
// nsIDOMEventListener
NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
private:
~FMRadio();
void SetCanPlay(bool aCanPlay);
hal::SwitchState mHeadphoneState;
bool mHasInternalAntenna;
bool mIsShutdown;
nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
};
END_FMRADIO_NAMESPACE

View File

@ -123,12 +123,8 @@ public:
info.spaceType() = mSpaceType;
EnableFMRadio(info);
IFMRadioService::Singleton()->EnableAudio(true);
nsCOMPtr<nsIAudioManager> audioManager =
do_GetService(NS_AUDIOMANAGER_CONTRACTID);
audioManager->SetFmRadioAudioEnabled(true);
// TODO apply path from bug 862899: AudioChannelAgent per process
return NS_OK;
}
@ -209,11 +205,7 @@ public:
// Fix Bug 796733. DisableFMRadio should be called before
// SetFmRadioAudioEnabled to prevent the annoying beep sound.
DisableFMRadio();
nsCOMPtr<nsIAudioManager> audioManager =
do_GetService(NS_AUDIOMANAGER_CONTRACTID);
audioManager->SetFmRadioAudioEnabled(false);
IFMRadioService::Singleton()->EnableAudio(false);
return NS_OK;
}
@ -299,6 +291,24 @@ FMRadioService::RemoveObserver(FMRadioEventObserver* aObserver)
}
}
void
FMRadioService::EnableAudio(bool aAudioEnabled)
{
MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
nsCOMPtr<nsIAudioManager> audioManager =
do_GetService("@mozilla.org/telephony/audiomanager;1");
if (!audioManager) {
return;
}
bool AudioEnabled;
audioManager->GetFmRadioAudioEnabled(&AudioEnabled);
if (AudioEnabled != aAudioEnabled) {
audioManager->SetFmRadioAudioEnabled(aAudioEnabled);
}
}
/**
* Round the frequency to match the range of frequency and the channel width. If
* the given frequency is out of range, return 0. For example:

View File

@ -117,6 +117,9 @@ public:
virtual void AddObserver(FMRadioEventObserver* aObserver) = 0;
virtual void RemoveObserver(FMRadioEventObserver* aObserver) = 0;
// Enable/Disable FMRadio
virtual void EnableAudio(bool aAudioEnabled) = 0;
/**
* Static method to return the singleton instance. If it's in the child
* process, we will get an object of FMRadioChild.
@ -164,6 +167,8 @@ public:
virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
/* FMRadioObserver */
void Notify(const hal::FMRadioOperationInformation& aInfo) MOZ_OVERRIDE;

View File

@ -165,6 +165,12 @@ FMRadioChild::DeallocPFMRadioRequestChild(PFMRadioRequestChild* aActor)
return true;
}
void
FMRadioChild::EnableAudio(bool aAudioEnabled)
{
SendEnableAudio(aAudioEnabled);
}
// static
FMRadioChild*
FMRadioChild::Singleton()

View File

@ -51,6 +51,8 @@ public:
virtual void AddObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
virtual void RemoveObserver(FMRadioEventObserver* aObserver) MOZ_OVERRIDE;
virtual void EnableAudio(bool aAudioEnabled) MOZ_OVERRIDE;
/* PFMRadioChild */
virtual bool
Recv__delete__() MOZ_OVERRIDE;

View File

@ -97,5 +97,12 @@ FMRadioParent::Notify(const FMRadioEventType& aType)
}
}
bool
FMRadioParent::RecvEnableAudio(const bool& aAudioEnabled)
{
IFMRadioService::Singleton()->EnableAudio(aAudioEnabled);
return true;
}
END_FMRADIO_NAMESPACE

View File

@ -33,6 +33,9 @@ public:
/* FMRadioEventObserver */
virtual void Notify(const FMRadioEventType& aType) MOZ_OVERRIDE;
virtual bool
RecvEnableAudio(const bool& aAudioEnabled) MOZ_OVERRIDE;
};
END_FMRADIO_NAMESPACE

View File

@ -86,6 +86,11 @@ parent:
* is more error prone.
*/
PFMRadioRequest(FMRadioRequestArgs requestType);
/**
* Enable/Disable audio
*/
EnableAudio(bool audioEnabled);
};
} // namespace dom

View File

@ -136,7 +136,6 @@ SystemMessageManager.prototype = {
dispatchers[aType] = { handler: aHandler, messages: [], isHandling: false };
// Ask for the list of currently pending messages.
this.addMessageListeners("SystemMessageManager:GetPendingMessages:Return");
cpmm.sendAsyncMessage("SystemMessageManager:GetPendingMessages",
{ type: aType,
uri: this._uri,
@ -215,8 +214,6 @@ SystemMessageManager.prototype = {
manifest: this._manifest,
uri: this._uri,
msgID: msg.msgID });
} else if (aMessage.name == "SystemMessageManager:GetPendingMessages:Return") {
this.removeMessageListeners(aMessage.name);
}
let messages = (aMessage.name == "SystemMessageManager:Message")
@ -251,7 +248,8 @@ SystemMessageManager.prototype = {
// nsIDOMGlobalPropertyInitializer implementation.
init: function sysMessMgr_init(aWindow) {
debug("init");
this.initDOMRequestHelper(aWindow, ["SystemMessageManager:Message"]);
this.initDOMRequestHelper(aWindow, ["SystemMessageManager:Message",
"SystemMessageManager:GetPendingMessages:Return"]);
let principal = aWindow.document.nodePrincipal;
this._isInBrowserElement = principal.isInBrowserElement;

View File

@ -80,6 +80,7 @@ class nsGeolocationRequest
void SendLocation(nsIDOMGeoPosition* location);
bool WantsHighAccuracy() {return !mShutdown && mOptions && mOptions->mEnableHighAccuracy;}
void SetTimeoutTimer();
void NotifyErrorAndShutdown(uint16_t);
nsIPrincipal* GetPrincipal();
~nsGeolocationRequest();
@ -350,6 +351,13 @@ NS_IMPL_CYCLE_COLLECTION_3(nsGeolocationRequest, mCallback, mErrorCallback, mLoc
NS_IMETHODIMP
nsGeolocationRequest::Notify(nsITimer* aTimer)
{
NotifyErrorAndShutdown(nsIDOMGeoPositionError::TIMEOUT);
return NS_OK;
}
void
nsGeolocationRequest::NotifyErrorAndShutdown(uint16_t aErrorCode)
{
MOZ_ASSERT(!mShutdown, "timeout after shutdown");
@ -358,13 +366,11 @@ nsGeolocationRequest::Notify(nsITimer* aTimer)
mLocator->RemoveRequest(this);
}
NotifyError(nsIDOMGeoPositionError::TIMEOUT);
NotifyError(aErrorCode);
if (!mShutdown) {
SetTimeoutTimer();
}
return NS_OK;
}
NS_IMETHODIMP
@ -1140,13 +1146,13 @@ Geolocation::NotifyError(uint16_t aErrorCode)
}
for (uint32_t i = mPendingCallbacks.Length(); i > 0; i--) {
mPendingCallbacks[i-1]->NotifyError(aErrorCode);
RemoveRequest(mPendingCallbacks[i-1]);
mPendingCallbacks[i-1]->NotifyErrorAndShutdown(aErrorCode);
//NotifyErrorAndShutdown() removes the request from the array
}
// notify everyone that is watching
for (uint32_t i = 0; i < mWatchingCallbacks.Length(); i++) {
mWatchingCallbacks[i]->NotifyError(aErrorCode);
mWatchingCallbacks[i]->NotifyErrorAndShutdown(aErrorCode);
}
return NS_OK;

View File

@ -175,22 +175,30 @@ WifiGeoPositionProvider.prototype = {
// This is a background load
xhr.open("POST", url, true);
try {
xhr.open("POST", url, true);
} catch (e) {
triggerError();
return;
}
xhr.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
xhr.responseType = "json";
xhr.mozBackgroundRequest = true;
xhr.channel.loadFlags = Ci.nsIChannel.LOAD_ANONYMOUS;
xhr.onerror = function() {
LOG("onerror: " + xhr);
triggerError();
};
xhr.onload = function() {
LOG("gls returned status: " + xhr.status + " --> " + JSON.stringify(xhr.response));
if (xhr.channel instanceof Ci.nsIHttpChannel && xhr.status != 200) {
triggerError();
return;
}
if (!xhr.response || !xhr.response.location) {
triggerError();
return;
}
@ -223,4 +231,8 @@ WifiGeoPositionProvider.prototype = {
},
};
function triggerError() {
Cc["@mozilla.org/geolocation/service;1"].getService(Ci.nsIGeolocationUpdate)
.notifyError(Ci.nsIDOMGeoPositionError.POSITION_UNAVAILABLE);
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiGeoPositionProvider]);

View File

@ -51,6 +51,11 @@ function delay_geolocationProvider(delay, callback)
SpecialPowers.pushPrefEnv({"set": [["geo.wifi.uri", "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs?delay=" + delay]]}, callback);
}
function send404_geolocationProvider(callback)
{
SpecialPowers.pushPrefEnv({"set": [["geo.wifi.uri", "http://mochi.test:8888/tests/dom/tests/mochitest/geolocation/network_geolocation.sjs?action=send404"]]}, callback);
}
function check_geolocation(location) {
ok(location, "Check to see if this location is non-null");

View File

@ -12,6 +12,7 @@ support-files =
[test_cancelWatch.html]
[test_clearWatch.html]
[test_clearWatch_invalid.html]
[test_errorcheck.html]
[test_geolocation_is_undefined_when_pref_is_off.html]
[test_handlerSpinsEventLoop.html]
[test_manyCurrentConcurrent.html]

View File

@ -37,6 +37,7 @@ function handleRequest(request, response)
var params = parseQueryString(request.queryString);
if (params.action == "stop-responding") {
response.processAsync();
return;
}
@ -64,6 +65,10 @@ function handleRequest(request, response)
if ('delay' in params) {
delay = params.delay;
}
if (params.action === "send404") {
response.setStatusLine("1.0", 404, "Not Found");
position = '';
}
timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer);
timer.initWithCallback(function() {
response.write(position);

View File

@ -0,0 +1,49 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=684722
-->
<head>
<title>Test for ErrorChecking </title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="geolocation_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=684722">Mozilla Bug 684722</a>
<p id="display"></p>
<div id="content" style="display: none">
</div>
<pre id="test">
<script class="testbody" type="text/javascript">
SimpleTest.waitForExplicitFinish();
resume_geolocationProvider(function() {
force_prompt(true, test1);
});
function test1() {
send404_geolocationProvider(test2);
}
function errorCallback(error) {
is(error.code,
SpecialPowers.Ci.nsIDOMGeoPositionError.POSITION_UNAVAILABLE, "Geolocation error handler fired");
SimpleTest.finish();
}
function successCallback(position) {
test2();
}
function test2() {
navigator.geolocation.getCurrentPosition(successCallback, errorCallback);
}
</script>
</pre>
</body>
</html>

View File

@ -77,10 +77,10 @@ interface CanvasRenderingContext2D {
// NOT IMPLEMENTED void fill(Path path);
void stroke();
// NOT IMPLEMENTED void stroke(Path path);
// NOT IMPLEMENTED void drawSystemFocusRing(Element element);
// NOT IMPLEMENTED void drawSystemFocusRing(Path path, Element element);
// NOT IMPLEMENTED boolean drawCustomFocusRing(Element element);
// NOT IMPLEMENTED boolean drawCustomFocusRing(Path path, Element element);
[Pref="canvas.focusring.enabled"] void drawSystemFocusRing(Element element);
// NOT IMPLEMENTED void drawSystemFocusRing(Path path, HTMLElement element);
[Pref="canvas.focusring.enabled"] boolean drawCustomFocusRing(Element element);
// NOT IMPLEMENTED boolean drawCustomFocusRing(Path path, HTMLElement element);
// NOT IMPLEMENTED void scrollPathIntoView();
// NOT IMPLEMENTED void scrollPathIntoView(Path path);
void clip(optional CanvasWindingRule winding = "nonzero");

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