mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Merge mozilla-central to UX
This commit is contained in:
commit
e8d8f80693
@ -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
|
||||
|
@ -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 \
|
||||
|
@ -1,4 +1,4 @@
|
||||
{
|
||||
"revision": "00ceae21c52602059b7614b661bc39a3c73c84de",
|
||||
"revision": "e9d3946c6e4c26c60f67b8efac40e14785b634d3",
|
||||
"repo_path": "/integration/gaia-central"
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 := ""
|
||||
|
@ -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;
|
||||
},
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
}
|
||||
},
|
||||
|
@ -95,5 +95,6 @@ function part8() {
|
||||
ok(objLoadingContent.activated, "plugin should be activated now");
|
||||
|
||||
gNewWindow.close();
|
||||
gNewWindow = null;
|
||||
finish();
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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.
|
||||
|
@ -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) {
|
||||
|
@ -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.
|
||||
|
@ -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();">
|
||||
|
@ -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.
|
||||
|
@ -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();">
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
});
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
68
browser/components/sessionstore/src/TabAttributes.jsm
Normal file
68
browser/components/sessionstore/src/TabAttributes.jsm
Normal 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]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
422
browser/components/sessionstore/src/TabState.jsm
Normal file
422
browser/components/sessionstore/src/TabState.jsm
Normal 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;
|
||||
}
|
||||
};
|
39
browser/components/sessionstore/src/Utils.jsm
Normal file
39
browser/components/sessionstore/src/Utils.jsm
Normal 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 == "/");
|
||||
}
|
||||
});
|
@ -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',
|
||||
]
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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))));
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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…">
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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',
|
||||
]
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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(?)
|
||||
])
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) \
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
40
content/canvas/src/WebGLExtensionSRGB.cpp
Normal file
40
content/canvas/src/WebGLExtensionSRGB.cpp
Normal 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)
|
@ -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
|
||||
{
|
||||
|
@ -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?");
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -55,6 +55,7 @@ if CONFIG['MOZ_WEBGL']:
|
||||
'WebGLExtensionElementIndexUint.cpp',
|
||||
'WebGLExtensionInstancedArrays.cpp',
|
||||
'WebGLExtensionLoseContext.cpp',
|
||||
'WebGLExtensionSRGB.cpp',
|
||||
'WebGLExtensionStandardDerivatives.cpp',
|
||||
'WebGLExtensionTextureFilterAnisotropic.cpp',
|
||||
'WebGLExtensionTextureFloat.cpp',
|
||||
|
@ -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]
|
||||
|
18
content/canvas/test/reftest/drawCustomFocusRing-ref.html
Normal file
18
content/canvas/test/reftest/drawCustomFocusRing-ref.html
Normal 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>
|
32
content/canvas/test/reftest/drawCustomFocusRing.html
Normal file
32
content/canvas/test/reftest/drawCustomFocusRing.html
Normal 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>
|
18
content/canvas/test/reftest/drawSystemFocusRing-ref.html
Normal file
18
content/canvas/test/reftest/drawSystemFocusRing-ref.html
Normal 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>
|
28
content/canvas/test/reftest/drawSystemFocusRing.html
Normal file
28
content/canvas/test/reftest/drawSystemFocusRing.html
Normal 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>
|
@ -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
|
||||
|
98
content/canvas/test/test_canvas_focusring.html
Normal file
98
content/canvas/test/test_canvas_focusring.html
Normal 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>
|
@ -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
|
||||
|
363
content/canvas/test/webgl/conformance/extensions/ext-sRGB.html
Normal file
363
content/canvas/test/webgl/conformance/extensions/ext-sRGB.html
Normal 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>
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -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();
|
||||
|
@ -1310,6 +1310,11 @@ DOMInterfaces = {
|
||||
'headerFile': 'WebGLExtensions.h'
|
||||
},
|
||||
|
||||
'WebGLExtensionSRGB': {
|
||||
'nativeType': 'mozilla::WebGLExtensionSRGB',
|
||||
'headerFile': 'WebGLExtensions.h'
|
||||
},
|
||||
|
||||
'WebGLExtensionStandardDerivatives': {
|
||||
'nativeType': 'mozilla::WebGLExtensionStandardDerivatives',
|
||||
'headerFile': 'WebGLExtensions.h'
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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;
|
||||
|
||||
|
@ -165,6 +165,12 @@ FMRadioChild::DeallocPFMRadioRequestChild(PFMRadioRequestChild* aActor)
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
FMRadioChild::EnableAudio(bool aAudioEnabled)
|
||||
{
|
||||
SendEnableAudio(aAudioEnabled);
|
||||
}
|
||||
|
||||
// static
|
||||
FMRadioChild*
|
||||
FMRadioChild::Singleton()
|
||||
|
@ -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;
|
||||
|
@ -97,5 +97,12 @@ FMRadioParent::Notify(const FMRadioEventType& aType)
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
FMRadioParent::RecvEnableAudio(const bool& aAudioEnabled)
|
||||
{
|
||||
IFMRadioService::Singleton()->EnableAudio(aAudioEnabled);
|
||||
return true;
|
||||
}
|
||||
|
||||
END_FMRADIO_NAMESPACE
|
||||
|
||||
|
@ -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
|
||||
|
@ -86,6 +86,11 @@ parent:
|
||||
* is more error prone.
|
||||
*/
|
||||
PFMRadioRequest(FMRadioRequestArgs requestType);
|
||||
|
||||
/**
|
||||
* Enable/Disable audio
|
||||
*/
|
||||
EnableAudio(bool audioEnabled);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
|
@ -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");
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
|
49
dom/tests/mochitest/geolocation/test_errorcheck.html
Normal file
49
dom/tests/mochitest/geolocation/test_errorcheck.html
Normal 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>
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user