diff --git a/aclocal.m4 b/aclocal.m4
index f90f089269bf..273f0d09de5b 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -32,7 +32,6 @@ builtin(include, build/autoconf/clang-plugin.m4)dnl
builtin(include, build/autoconf/alloc.m4)dnl
builtin(include, build/autoconf/ios.m4)dnl
builtin(include, build/autoconf/jemalloc.m4)dnl
-builtin(include, build/autoconf/rust.m4)dnl
MOZ_PROG_CHECKMSYS()
diff --git a/b2g/app/nsBrowserApp.cpp b/b2g/app/nsBrowserApp.cpp
index ab3c18b658de..8e6d2651b0db 100644
--- a/b2g/app/nsBrowserApp.cpp
+++ b/b2g/app/nsBrowserApp.cpp
@@ -215,17 +215,6 @@ int main(int argc, _CONST char* argv[])
(void)setsid();
#endif
- int gotCounters;
-#if defined(XP_UNIX)
- struct rusage initialRUsage;
- gotCounters = !getrusage(RUSAGE_SELF, &initialRUsage);
-#elif defined(XP_WIN)
- IO_COUNTERS ioCounters;
- gotCounters = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
-#else
- #error "Unknown platform" // having this here keeps cppcheck happy
-#endif
-
#ifdef HAS_DLL_BLOCKLIST
DllBlocklist_Initialize();
#endif
@@ -251,32 +240,6 @@ int main(int argc, _CONST char* argv[])
return 255;
}
- if (gotCounters) {
-#if defined(XP_WIN)
- XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_OPS,
- int(ioCounters.ReadOperationCount));
- XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_TRANSFER,
- int(ioCounters.ReadTransferCount / 1024));
- IO_COUNTERS newIoCounters;
- if (GetProcessIoCounters(GetCurrentProcess(), &newIoCounters)) {
- XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_OPS,
- int(newIoCounters.ReadOperationCount - ioCounters.ReadOperationCount));
- XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_TRANSFER,
- int((newIoCounters.ReadTransferCount - ioCounters.ReadTransferCount) / 1024));
- }
-#elif defined(XP_UNIX)
- XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_HARD_FAULTS,
- int(initialRUsage.ru_majflt));
- struct rusage newRUsage;
- if (!getrusage(RUSAGE_SELF, &newRUsage)) {
- XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_HARD_FAULTS,
- int(newRUsage.ru_majflt - initialRUsage.ru_majflt));
- }
-#else
- #error "Unknown platform" // having this here keeps cppcheck happy
-#endif
- }
-
int result;
{
ScopedLogging log;
diff --git a/browser/app/nsBrowserApp.cpp b/browser/app/nsBrowserApp.cpp
index 9b48033e25e3..b88d79249392 100644
--- a/browser/app/nsBrowserApp.cpp
+++ b/browser/app/nsBrowserApp.cpp
@@ -362,17 +362,6 @@ int main(int argc, char* argv[], char* envp[])
TriggerQuirks();
#endif
- int gotCounters;
-#if defined(XP_UNIX)
- struct rusage initialRUsage;
- gotCounters = !getrusage(RUSAGE_SELF, &initialRUsage);
-#elif defined(XP_WIN)
- IO_COUNTERS ioCounters;
- gotCounters = GetProcessIoCounters(GetCurrentProcess(), &ioCounters);
-#else
- #error "Unknown platform" // having this here keeps cppcheck happy
-#endif
-
nsIFile *xreDirectory;
#ifdef HAS_DLL_BLOCKLIST
@@ -394,32 +383,6 @@ int main(int argc, char* argv[], char* envp[])
XRE_StartupTimelineRecord(mozilla::StartupTimeline::START, start);
- if (gotCounters) {
-#if defined(XP_WIN)
- XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_OPS,
- int(ioCounters.ReadOperationCount));
- XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_READ_TRANSFER,
- int(ioCounters.ReadTransferCount / 1024));
- IO_COUNTERS newIoCounters;
- if (GetProcessIoCounters(GetCurrentProcess(), &newIoCounters)) {
- XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_OPS,
- int(newIoCounters.ReadOperationCount - ioCounters.ReadOperationCount));
- XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_READ_TRANSFER,
- int((newIoCounters.ReadTransferCount - ioCounters.ReadTransferCount) / 1024));
- }
-#elif defined(XP_UNIX)
- XRE_TelemetryAccumulate(mozilla::Telemetry::EARLY_GLUESTARTUP_HARD_FAULTS,
- int(initialRUsage.ru_majflt));
- struct rusage newRUsage;
- if (!getrusage(RUSAGE_SELF, &newRUsage)) {
- XRE_TelemetryAccumulate(mozilla::Telemetry::GLUESTARTUP_HARD_FAULTS,
- int(newRUsage.ru_majflt - initialRUsage.ru_majflt));
- }
-#else
- #error "Unknown platform" // having this here keeps cppcheck happy
-#endif
- }
-
#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
XRE_EnableSameExecutableForContentProc();
#endif
diff --git a/browser/base/content/browser-fxaccounts.js b/browser/base/content/browser-fxaccounts.js
index 3af34e9239a2..11c2c22e2b2a 100644
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -229,7 +229,7 @@ var gFxAccounts = {
this.panelUIFooter.removeAttribute("fxaprofileimage");
this.panelUIAvatar.style.removeProperty("list-style-image");
let showErrorBadge = false;
- if (!this._inCustomizationMode && userData) {
+ if (userData) {
// At this point we consider the user as logged-in (but still can be in an error state)
if (this.loginFailed) {
let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [userData.email], 1);
@@ -260,7 +260,7 @@ var gFxAccounts = {
}
let updateWithProfile = (profile) => {
- if (!this._inCustomizationMode && profileInfoEnabled) {
+ if (profileInfoEnabled) {
if (profile.displayName) {
this.panelUILabel.setAttribute("label", profile.displayName);
}
diff --git a/browser/base/content/browser-gestureSupport.js b/browser/base/content/browser-gestureSupport.js
index a29f733eeec1..d356d5aba454 100644
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -987,7 +987,6 @@ var gHistorySwipeAnimation = {
let canvas = null;
- TelemetryStopwatch.start("FX_GESTURE_TAKE_SNAPSHOT_OF_PAGE");
try {
let browser = gBrowser.selectedBrowser;
let r = browser.getBoundingClientRect();
@@ -1006,7 +1005,6 @@ var gHistorySwipeAnimation = {
ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
} finally {
- TelemetryStopwatch.finish("FX_GESTURE_TAKE_SNAPSHOT_OF_PAGE");
}
TelemetryStopwatch.start("FX_GESTURE_INSTALL_SNAPSHOT_OF_PAGE");
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
index 31603fad2af6..6c4707696eb5 100644
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -243,6 +243,10 @@ toolbar[customizing] > .overflow-button {
-moz-appearance: -moz-window-button-box;
}
+#personal-bookmarks {
+ -moz-window-dragging: inherit;
+}
+
%ifdef XP_MACOSX
#titlebar-fullscreen-button {
-moz-appearance: -moz-mac-fullscreen-button;
@@ -286,8 +290,8 @@ toolbar[customizing] > .overflow-button {
}
#main-window[tabsintitlebar] #TabsToolbar,
-#main-window[tabsintitlebar] #toolbar-menubar:not([autohide=true]),
-#main-window[tabsintitlebar] #navigator-toolbox > toolbar:not(#toolbar-menubar):-moz-lwtheme {
+#main-window[tabsintitlebar] #toolbar-menubar,
+#main-window[tabsintitlebar] #navigator-toolbox > toolbar:-moz-lwtheme {
-moz-window-dragging: drag;
}
%endif
@@ -360,16 +364,6 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks {
-moz-box-flex: 1;
}
-/* Ensure that empty parts of the bookmarks container can be dragged on OSX, and on other OSes
- * only when a lwtheme is in use. */
-%ifdef XP_MACOSX
-#main-window[tabsintitlebar]:not([customizing]) #personal-bookmarks {
-%else
-#main-window[tabsintitlebar]:not([customizing]) #personal-bookmarks:-moz-lwtheme {
-%endif
- -moz-window-dragging: drag;
-}
-
#zoom-controls[cui-areatype="toolbar"]:not([overflowedItem=true]) > #zoom-reset-button > .toolbarbutton-text {
display: -moz-box;
}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
index 0b6f29f8f771..6117295bdd1d 100755
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6574,13 +6574,25 @@ var gIdentityHandler = {
delete this._identityBox;
return this._identityBox = document.getElementById("identity-box");
},
+ get _identityPopupMultiView () {
+ delete _identityPopupMultiView;
+ return document.getElementById("identity-popup-multiView");
+ },
get _identityPopupContentHosts () {
delete this._identityPopupContentHosts;
- return this._identityPopupContentHosts = [...document.querySelectorAll(".identity-popup-headline.host")];
+ let selector = ".identity-popup-headline.host";
+ return this._identityPopupContentHosts = [
+ ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+ ...document.querySelectorAll(selector)
+ ];
},
get _identityPopupContentHostless () {
delete this._identityPopupContentHostless;
- return this._identityPopupContentHostless = [...document.querySelectorAll(".identity-popup-headline.hostless")];
+ let selector = ".identity-popup-headline.hostless";
+ return this._identityPopupContentHostless = [
+ ...this._identityPopupMultiView._mainView.querySelectorAll(selector),
+ ...document.querySelectorAll(selector)
+ ];
},
get _identityPopupContentOwner () {
delete this._identityPopupContentOwner;
@@ -6640,7 +6652,7 @@ var gIdentityHandler = {
},
toggleSubView(name, anchor) {
- let view = document.getElementById("identity-popup-multiView");
+ let view = this._identityPopupMultiView;
if (view.showingSubView) {
view.showMainView();
} else {
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
index b938de197ecf..dfc3b15b6049 100644
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5625,10 +5625,6 @@
return;
}
- Services.telemetry.getHistogramById(aTab.closing ?
- "FX_TAB_ANIM_CLOSE_MS" :
- "FX_TAB_ANIM_OPEN_MS")
- .add(Date.now() - aTab._animStartTime);
aTab._animStartTime = 0;
// Handle tab animation smoothness telemetry/logging of frame intervals and paint times
diff --git a/browser/base/content/test/general/head.js b/browser/base/content/test/general/head.js
index c378b8769e50..056c24895041 100644
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -767,7 +767,6 @@ function assertMixedContentBlockingState(tabbrowser, states = {}) {
// Make sure the correct icon is visible in the Control Center.
// This logic is controlled with CSS, so this helps prevent regressions there.
let securityView = doc.getElementById("identity-popup-securityView");
- let securityContent = doc.getElementById("identity-popup-security-content");
let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
getPropertyValue("background-image");
let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
diff --git a/browser/components/migration/360seProfileMigrator.js b/browser/components/migration/360seProfileMigrator.js
index c4fc8693df97..a65f96243bae 100644
--- a/browser/components/migration/360seProfileMigrator.js
+++ b/browser/components/migration/360seProfileMigrator.js
@@ -18,6 +18,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
+const kBookmarksFileName = "360sefav.db";
+
function copyToTempUTF8File(file, charset) {
let inputStream = Cc["@mozilla.org/network/file-input-stream;1"]
.createInstance(Ci.nsIFileInputStream);
@@ -95,7 +97,7 @@ function getHash(aStr) {
function Bookmarks(aProfileFolder) {
let file = aProfileFolder.clone();
- file.append("360sefav.db");
+ file.append(kBookmarksFileName);
this._file = file;
}
@@ -298,6 +300,23 @@ Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
return resources.filter(r => r.exists);
};
+Qihoo360seProfileMigrator.prototype.getLastUsedDate = function() {
+ let bookmarksPaths = this.sourceProfiles.map(({id}) => {
+ return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
+ });
+ if (!bookmarksPaths.length) {
+ return Promise.resolve(new Date(0));
+ }
+ let datePromises = bookmarksPaths.map(path => {
+ return OS.File.stat(path).catch(_ => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
Qihoo360seProfileMigrator.prototype.classDescription = "360 Secure Browser Profile Migrator";
Qihoo360seProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=360se";
Qihoo360seProfileMigrator.prototype.classID = Components.ID("{d0037b95-296a-4a4e-94b2-c3d075d20ab1}");
diff --git a/browser/components/migration/AutoMigrate.jsm b/browser/components/migration/AutoMigrate.jsm
index 3561699078fe..320d66964764 100644
--- a/browser/components/migration/AutoMigrate.jsm
+++ b/browser/components/migration/AutoMigrate.jsm
@@ -8,13 +8,19 @@ this.EXPORTED_SYMBOLS = ["AutoMigrate"];
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
+const kAutoMigrateStartedPref = "browser.migrate.automigrate-started";
+const kAutoMigrateFinishedPref = "browser.migrate.automigrate-finished";
+
Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const AutoMigrate = {
get resourceTypesToUse() {
- let {BOOKMARKS, HISTORY, FORMDATA, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
- return BOOKMARKS | HISTORY | FORMDATA | PASSWORDS;
+ let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ return BOOKMARKS | HISTORY | PASSWORDS;
},
/**
@@ -25,35 +31,40 @@ const AutoMigrate = {
* failed for some reason.
*/
migrate(profileStartup, migratorKey, profileToMigrate) {
- let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_SUCCEEDED");
- histogram.add("initialized");
+ let histogram = Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_AUTOMATED_IMPORT_PROCESS_SUCCESS");
+ histogram.add(0);
let migrator = this.pickMigrator(migratorKey);
- histogram.add("got-browser");
+ histogram.add(5);
profileToMigrate = this.pickProfile(migrator, profileToMigrate);
- histogram.add("got-profile");
+ histogram.add(10);
let resourceTypes = migrator.getMigrateData(profileToMigrate, profileStartup);
if (!(resourceTypes & this.resourceTypesToUse)) {
throw new Error("No usable resources were found for the selected browser!");
}
- histogram.add("got-data");
+ histogram.add(15);
let sawErrors = false;
let migrationObserver = function(subject, topic, data) {
if (topic == "Migration:ItemError") {
sawErrors = true;
} else if (topic == "Migration:Ended") {
- histogram.add(sawErrors ? "finished-with-errors" : "finished");
+ histogram.add(25);
+ if (sawErrors) {
+ histogram.add(26);
+ }
Services.obs.removeObserver(migrationObserver, "Migration:Ended");
Services.obs.removeObserver(migrationObserver, "Migration:ItemError");
+ Services.prefs.setCharPref(kAutoMigrateFinishedPref, Date.now().toString());
}
};
Services.obs.addObserver(migrationObserver, "Migration:Ended", false);
Services.obs.addObserver(migrationObserver, "Migration:ItemError", false);
+ Services.prefs.setCharPref(kAutoMigrateStartedPref, Date.now().toString());
migrator.migrate(this.resourceTypesToUse, profileStartup, profileToMigrate);
- histogram.add("migrate-called-without-exceptions");
+ histogram.add(20);
},
/**
@@ -109,5 +120,59 @@ const AutoMigrate = {
}
return profiles ? profiles[0].id : null;
},
+
+ getUndoRange() {
+ let start, finish;
+ try {
+ start = parseInt(Services.prefs.getCharPref(kAutoMigrateStartedPref), 10);
+ finish = parseInt(Services.prefs.getCharPref(kAutoMigrateFinishedPref), 10);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ if (!finish || !start) {
+ return null;
+ }
+ return [new Date(start), new Date(finish)];
+ },
+
+ canUndo() {
+ if (!this.getUndoRange()) {
+ return Promise.resolve(false);
+ }
+ // Return a promise resolving to false if we're signed into sync, resolve
+ // to true otherwise.
+ let {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
+ return fxAccounts.getSignedInUser().then(user => !user, () => Promise.resolve(true));
+ },
+
+ undo: Task.async(function* () {
+ if (!(yield this.canUndo())) {
+ throw new Error("Can't undo!");
+ }
+
+ yield PlacesUtils.bookmarks.eraseEverything();
+
+ // NB: we drop the start time of the migration for now. This is because
+ // imported history will always end up being 'backdated' to the actual
+ // visit time recorded by the browser from which we imported. As a result,
+ // a lower bound on this item doesn't really make sense.
+ // Note that for form data this could be different, but we currently don't
+ // support form data import from any non-Firefox browser, so it isn't
+ // imported from other browsers by the automigration code, nor do we
+ // remove it here.
+ let range = this.getUndoRange();
+ yield PlacesUtils.history.removeVisitsByFilter({
+ beginDate: new Date(0),
+ endDate: range[1]
+ });
+
+ try {
+ Services.logins.removeAllLogins();
+ } catch (ex) {
+ // ignore failure.
+ }
+ Services.prefs.clearUserPref("browser.migrate.automigrate-started");
+ Services.prefs.clearUserPref("browser.migrate.automigrate-finished");
+ }),
};
diff --git a/browser/components/migration/ChromeProfileMigrator.js b/browser/components/migration/ChromeProfileMigrator.js
index 6ab5ab36f41b..5bdf13e2049b 100644
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -24,6 +24,8 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Console.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
@@ -136,6 +138,27 @@ ChromeProfileMigrator.prototype.getResources =
return [];
};
+ChromeProfileMigrator.prototype.getLastUsedDate =
+ function Chrome_getLastUsedDate() {
+ let datePromises = this.sourceProfiles.map(profile => {
+ let profileFolder = this._chromeUserDataFolder.clone();
+ let basePath = OS.Path.join(this._chromeUserDataFolder.path, profile.id);
+ let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(leafName => {
+ let path = OS.Path.join(basePath, leafName);
+ return OS.File.stat(path).catch(_ => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ return Promise.all(fileDatePromises).then(dates => {
+ return Math.max.apply(Math, dates);
+ });
+ });
+ return Promise.all(datePromises).then(dates => {
+ dates.push(0);
+ return new Date(Math.max.apply(Math, dates));
+ });
+ };
+
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
get: function Chrome_sourceProfiles() {
if ("__sourceProfiles" in this)
diff --git a/browser/components/migration/EdgeProfileMigrator.js b/browser/components/migration/EdgeProfileMigrator.js
index b4eb8d5ffac4..cc67582356dc 100644
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -4,10 +4,11 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
@@ -420,6 +421,34 @@ EdgeProfileMigrator.prototype.getResources = function() {
return resources.filter(r => r.exists);
};
+EdgeProfileMigrator.prototype.getLastUsedDate = function() {
+ // Don't do this if we don't have a single profile (see the comment for
+ // sourceProfiles) or if we can't find the database file:
+ if (this.sourceProfiles !== null || !gEdgeDatabase) {
+ return Promise.resolve(new Date(0));
+ }
+ let logFilePath = OS.Path.join(gEdgeDatabase.parent.path, "LogFiles", "edb.log");
+ let dbPath = gEdgeDatabase.path;
+ let cookieMigrator = MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE);
+ let cookiePaths = cookieMigrator._cookiesFolders.map(f => f.path);
+ let datePromises = [logFilePath, dbPath, ... cookiePaths].map(path => {
+ return OS.File.stat(path).catch(_ => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ datePromises.push(new Promise(resolve => {
+ let typedURLs = new Map();
+ try {
+ typedURLs = MSMigrationUtils.getTypedURLs(kEdgeRegistryRoot);
+ } catch (ex) {}
+ let times = [0, ... typedURLs.values()];
+ resolve(Math.max.apply(Math, times));
+ }));
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
/* Somewhat counterintuitively, this returns:
* - |null| to indicate "There is only 1 (default) profile" (on win10+)
* - |[]| to indicate "There are no profiles" (on <=win8.1) which will avoid using this migrator.
diff --git a/browser/components/migration/FirefoxProfileMigrator.js b/browser/components/migration/FirefoxProfileMigrator.js
index c42f6d32a311..b07f51ec10f3 100644
--- a/browser/components/migration/FirefoxProfileMigrator.js
+++ b/browser/components/migration/FirefoxProfileMigrator.js
@@ -97,6 +97,13 @@ FirefoxProfileMigrator.prototype.getResources = function(aProfile) {
return this._getResourcesInternal(sourceProfileDir, currentProfileDir, aProfile);
};
+FirefoxProfileMigrator.prototype.getLastUsedDate = function() {
+ // We always pretend we're really old, so that we don't mess
+ // up the determination of which browser is the most 'recent'
+ // to import from.
+ return Promise.resolve(new Date(0));
+};
+
FirefoxProfileMigrator.prototype._getResourcesInternal = function(sourceProfileDir, currentProfileDir, aProfile) {
let getFileResource = function(aMigrationType, aFileNames) {
let files = [];
diff --git a/browser/components/migration/IEProfileMigrator.js b/browser/components/migration/IEProfileMigrator.js
index 8d2e52961e20..e42ba7144906 100644
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -13,9 +13,10 @@ const kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storag
const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
Cu.import("resource:///modules/MSMigrationUtils.jsm");
Cu.import("resource://gre/modules/LoginHelper.jsm");
@@ -495,6 +496,26 @@ IEProfileMigrator.prototype.getResources = function IE_getResources() {
return resources.filter(r => r.exists);
};
+IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
+ let datePromises = ["Favs", "CookD"].map(dirId => {
+ let {path} = Services.dirsvc.get(dirId, Ci.nsIFile);
+ return OS.File.stat(path).catch(_ => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ datePromises.push(new Promise(resolve => {
+ let typedURLs = new Map();
+ try {
+ typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
+ } catch (ex) {}
+ let dates = [0, ... typedURLs.values()];
+ resolve(Math.max.apply(Math, dates));
+ }));
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
get: function IE_get_sourceHomePageURL() {
let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
diff --git a/browser/components/migration/MigrationUtils.jsm b/browser/components/migration/MigrationUtils.jsm
index b868dd72d6f2..f757099b57f2 100644
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -137,6 +137,20 @@ this.MigratorPrototype = {
throw new Error("getResources must be overridden");
},
+ /**
+ * OVERRIDE in order to provide an estimate of when the last time was
+ * that somebody used the browser. It is OK that this is somewhat fuzzy -
+ * history may not be available (or be wiped or not present due to e.g.
+ * incognito mode).
+ *
+ * @return a Promise that resolves to the last used date.
+ *
+ * @note If not overridden, the promise will resolve to the unix epoch.
+ */
+ getLastUsedDate() {
+ return Promise.resolve(new Date(0));
+ },
+
/**
* OVERRIDE IF AND ONLY IF the migrator is a startup-only migrator (For now,
* that is just the Firefox migrator, see bug 737381). Default: false.
diff --git a/browser/components/migration/SafariProfileMigrator.js b/browser/components/migration/SafariProfileMigrator.js
index d3f93225dd70..8cc9236fa955 100644
--- a/browser/components/migration/SafariProfileMigrator.js
+++ b/browser/components/migration/SafariProfileMigrator.js
@@ -9,10 +9,11 @@ var Ci = Components.interfaces;
var Cu = Components.utils;
Cu.import("resource://gre/modules/AppConstants.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource:///modules/MigrationUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
@@ -640,6 +641,24 @@ SafariProfileMigrator.prototype.getResources = function SM_getResources() {
return resources;
};
+SafariProfileMigrator.prototype.getLastUsedDate = function SM_getLastUsedDate() {
+ let profileDir;
+ if (AppConstants.platform == "macosx") {
+ profileDir = FileUtils.getDir("ULibDir", ["Safari"], false);
+ } else {
+ profileDir = FileUtils.getDir("AppData", ["Apple Computer", "Safari"], false);
+ }
+ let datePromises = ["Bookmarks.plist", "History.plist"].map(file => {
+ let path = OS.Path.join(profileDir.path, file);
+ return OS.File.stat(path).catch(_ => null).then(info => {
+ return info ? info.lastModificationDate : 0;
+ });
+ });
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
Object.defineProperty(SafariProfileMigrator.prototype, "mainPreferencesPropertyList", {
get: function get_mainPreferencesPropertyList() {
if (this._mainPreferencesPropertyList === undefined) {
diff --git a/browser/components/migration/content/migration.js b/browser/components/migration/content/migration.js
index f8d423940604..f58d9326aa42 100644
--- a/browser/components/migration/content/migration.js
+++ b/browser/components/migration/content/migration.js
@@ -83,10 +83,10 @@ var MigrationWizard = {
this._wiz.canRewind = false;
var selectedMigrator = null;
+ this._availableMigrators = [];
// Figure out what source apps are are available to import from:
var group = document.getElementById("importSourceGroup");
- var availableMigratorCount = 0;
for (var i = 0; i < group.childNodes.length; ++i) {
var migratorKey = group.childNodes[i].id;
if (migratorKey != "nothing") {
@@ -96,7 +96,7 @@ var MigrationWizard = {
// one, or if it is the migrator that was passed to us.
if (!selectedMigrator || this._source == migratorKey)
selectedMigrator = group.childNodes[i];
- availableMigratorCount++;
+ this._availableMigrators.push([migratorKey, migrator]);
} else {
// Hide this option
group.childNodes[i].hidden = true;
@@ -105,7 +105,7 @@ var MigrationWizard = {
}
if (this.isInitialMigration) {
Services.telemetry.getHistogramById("FX_STARTUP_MIGRATION_BROWSER_COUNT")
- .add(availableMigratorCount);
+ .add(this._availableMigrators.length);
let defaultBrowser = MigrationUtils.getMigratorKeyForDefaultBrowser();
// This will record 0 for unknown default browser IDs.
defaultBrowser = MigrationUtils.getSourceIdForTelemetry(defaultBrowser);
@@ -448,6 +448,9 @@ var MigrationWizard = {
label.removeAttribute("style");
break;
case "Migration:Ended":
+ if (this.isInitialMigration) {
+ this.reportDataRecencyTelemetry();
+ }
if (this._autoMigrate) {
Services.telemetry.getKeyedHistogramById("FX_MIGRATION_HOMEPAGE_IMPORTED")
.add(this._source, !!this._newHomePage);
@@ -532,5 +535,31 @@ var MigrationWizard = {
this._wiz.getButton("cancel").disabled = true;
this._wiz.canRewind = false;
this._listItems("doneItems");
- }
+ },
+
+ reportDataRecencyTelemetry() {
+ let histogram = Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_DATA_RECENCY");
+ let lastUsedPromises = [];
+ for (let [key, migrator] of this._availableMigrators) {
+ // No block-scoped let in for...of loop conditions, so get the source:
+ let localKey = key;
+ lastUsedPromises.push(migrator.getLastUsedDate().then(date => {
+ const ONE_YEAR = 24 * 365;
+ let diffInHours = Math.round((Date.now() - date) / (60 * 60 * 1000));
+ if (diffInHours > ONE_YEAR) {
+ diffInHours = ONE_YEAR;
+ }
+ histogram.add(localKey, diffInHours);
+ return [localKey, diffInHours];
+ }));
+ }
+ Promise.all(lastUsedPromises).then(migratorUsedTimeDiff => {
+ // Sort low to high.
+ migratorUsedTimeDiff.sort(([keyA, diffA], [keyB, diffB]) => diffA - diffB);
+ let usedMostRecentBrowser = migratorUsedTimeDiff.length && this._source == migratorUsedTimeDiff[0][0];
+ let usedRecentBrowser =
+ Services.telemetry.getKeyedHistogramById("FX_STARTUP_MIGRATION_USED_RECENT_BROWSER");
+ usedRecentBrowser.add(this._source, usedMostRecentBrowser);
+ });
+ },
};
diff --git a/browser/components/migration/nsIBrowserProfileMigrator.idl b/browser/components/migration/nsIBrowserProfileMigrator.idl
index a79f31711458..a251c3683a08 100644
--- a/browser/components/migration/nsIBrowserProfileMigrator.idl
+++ b/browser/components/migration/nsIBrowserProfileMigrator.idl
@@ -43,6 +43,12 @@ interface nsIBrowserProfileMigrator : nsISupports
*/
unsigned short getMigrateData(in jsval aProfile, in boolean aDoingStartup);
+ /**
+ * Get the last time data from this browser was modified
+ * @return a promise that resolves to a JS Date object
+ */
+ jsval getLastUsedDate();
+
/**
* Whether or not there is any data that can be imported from this
* browser (i.e. whether or not it is installed, and there exists
diff --git a/browser/components/migration/tests/unit/test_automigration.js b/browser/components/migration/tests/unit/test_automigration.js
index 1d47f031272b..ccd212d537d7 100644
--- a/browser/components/migration/tests/unit/test_automigration.js
+++ b/browser/components/migration/tests/unit/test_automigration.js
@@ -1,9 +1,14 @@
Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+Cu.import("resource://testing-common/PlacesTestUtils.jsm");
let AutoMigrateBackstage = Cu.import("resource:///modules/AutoMigrate.jsm");
let gShimmedMigratorKeyPicker = null;
let gShimmedMigrator = null;
+const kUsecPerMin = 60 * 1000000;
+
// This is really a proxy on MigrationUtils, but if we specify that directly,
// we get in trouble because the object itself is frozen, and Proxies can't
// return a different value to an object when directly proxying a frozen
@@ -112,8 +117,133 @@ add_task(function* checkIntegration() {
Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
"getMigrateData called with 'null' as a profile");
- let {BOOKMARKS, HISTORY, FORMDATA, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
- let expectedTypes = BOOKMARKS | HISTORY | FORMDATA | PASSWORDS;
+ let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
- "getMigrateData called with 'null' as a profile");
+ "migrate called with 'null' as a profile");
+});
+
+/**
+ * Test the undo preconditions and a no-op undo in the automigrator.
+ */
+add_task(function* checkUndoPreconditions() {
+ gShimmedMigrator = {
+ get sourceProfiles() {
+ do_print("Read sourceProfiles");
+ return null;
+ },
+ getMigrateData(profileToMigrate) {
+ this._getMigrateDataArgs = profileToMigrate;
+ return Ci.nsIBrowserProfileMigrator.BOOKMARKS;
+ },
+ migrate(types, startup, profileToMigrate) {
+ this._migrateArgs = [types, startup, profileToMigrate];
+ TestUtils.executeSoon(function() {
+ Services.obs.notifyObservers(null, "Migration:Ended", undefined);
+ });
+ },
+ };
+
+ gShimmedMigratorKeyPicker = function() {
+ return "gobbledygook";
+ };
+ AutoMigrate.migrate("startup");
+ let migrationFinishedPromise = TestUtils.topicObserved("Migration:Ended");
+ Assert.strictEqual(gShimmedMigrator._getMigrateDataArgs, null,
+ "getMigrateData called with 'null' as a profile");
+
+ let {BOOKMARKS, HISTORY, PASSWORDS} = Ci.nsIBrowserProfileMigrator;
+ let expectedTypes = BOOKMARKS | HISTORY | PASSWORDS;
+ Assert.deepEqual(gShimmedMigrator._migrateArgs, [expectedTypes, "startup", null],
+ "migrate called with 'null' as a profile");
+
+ yield migrationFinishedPromise;
+ Assert.ok(Services.prefs.getPrefType("browser.migrate.automigrate-started"),
+ "Should have set start time pref");
+ Assert.ok(Services.prefs.getPrefType("browser.migrate.automigrate-finished"),
+ "Should have set finish time pref");
+ Assert.ok((yield AutoMigrate.canUndo()), "Should be able to undo migration");
+
+ let [beginRange, endRange] = AutoMigrate.getUndoRange();
+ let stringRange = `beginRange: ${beginRange}; endRange: ${endRange}`;
+ Assert.ok(beginRange <= endRange,
+ "Migration should have started before or when it ended " + stringRange);
+
+ yield AutoMigrate.undo();
+ Assert.ok(true, "Should be able to finish an undo cycle.");
+});
+
+/**
+ * Fake a migration and then try to undo it to verify all data gets removed.
+ */
+add_task(function* checkUndoRemoval() {
+ let startTime = "" + Date.now();
+ Services.prefs.setCharPref("browser.migrate.automigrate-started", startTime);
+
+ // Insert a login and check that that worked.
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
+ login.init("www.mozilla.org", "http://www.mozilla.org", null, "user", "pass", "userEl", "passEl");
+ Services.logins.addLogin(login);
+ let storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+ "http://www.mozilla.org", null);
+ Assert.equal(storedLogins.length, 1, "Should have 1 login");
+
+ // Insert a bookmark and check that we have exactly 1 bookmark for that URI.
+ yield PlacesUtils.bookmarks.insert({
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ url: "http://www.example.org/",
+ title: "Some example bookmark",
+ });
+
+ let bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
+ Assert.ok(bookmark, "Should have a bookmark before undo");
+ Assert.equal(bookmark.title, "Some example bookmark", "Should have correct bookmark before undo.");
+
+ // Insert 2 history visits - one in the current migration time, one from before.
+ let now_uSec = Date.now() * 1000;
+ let visitedURI = Services.io.newURI("http://www.example.com/", null, null);
+ yield PlacesTestUtils.addVisits([
+ {uri: visitedURI, visitDate: now_uSec},
+ {uri: visitedURI, visitDate: now_uSec - 100 * kUsecPerMin},
+ ]);
+
+ // Verify that both visits get reported.
+ let opts = PlacesUtils.history.getNewQueryOptions();
+ opts.resultType = opts.RESULTS_AS_VISIT;
+ let query = PlacesUtils.history.getNewQuery();
+ query.uri = visitedURI;
+ let visits = PlacesUtils.history.executeQuery(query, opts);
+ visits.root.containerOpen = true;
+ Assert.equal(visits.root.childCount, 2, "Should have 2 visits");
+ // Clean up:
+ visits.root.containerOpen = false;
+
+ // Now set finished pref:
+ let endTime = "" + Date.now();
+ Services.prefs.setCharPref("browser.migrate.automigrate-finished", endTime);
+
+ // Verify that we can undo, then undo:
+ Assert.ok(yield AutoMigrate.canUndo(), "Should be possible to undo migration");
+ yield AutoMigrate.undo();
+
+ // Check that the undo removed the history visits:
+ visits = PlacesUtils.history.executeQuery(query, opts);
+ visits.root.containerOpen = true;
+ Assert.equal(visits.root.childCount, 0, "Should have no more visits");
+ visits.root.containerOpen = false;
+
+ // Check that the undo removed the bookmarks:
+ bookmark = yield PlacesUtils.bookmarks.fetch({url: "http://www.example.org/"});
+ Assert.ok(!bookmark, "Should have no bookmarks after undo");
+
+ // Check that the undo removed the passwords:
+ storedLogins = Services.logins.findLogins({}, "www.mozilla.org",
+ "http://www.mozilla.org", null);
+ Assert.equal(storedLogins.length, 0, "Should have no logins");
+
+ // Finally check prefs got cleared:
+ Assert.ok(!Services.prefs.getPrefType("browser.migrate.automigrate-started"),
+ "Should no longer have pref for migration start time.");
+ Assert.ok(!Services.prefs.getPrefType("browser.migrate.automigrate-finished"),
+ "Should no longer have pref for migration finish time.");
});
diff --git a/browser/components/preferences/fonts.xul b/browser/components/preferences/fonts.xul
index 172ea440a3f6..ed1d1ecc21b0 100644
--- a/browser/components/preferences/fonts.xul
+++ b/browser/components/preferences/fonts.xul
@@ -68,7 +68,7 @@
-
+
diff --git a/browser/components/preferences/in-content/privacy.xul b/browser/components/preferences/in-content/privacy.xul
index f67ba9ba202d..3f9de6839ced 100644
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -136,9 +136,9 @@
- &doNotTrack.pre.label;&doNotTrack.post.label;
+ &doNotTrack.pre.label;&doNotTrack.settings.label;&doNotTrack.post.label;
@@ -165,11 +165,11 @@
&rememberDescription.label;
- &rememberActions.pre.label;&rememberActions.middle.label;&rememberActions.post.label;
+ &rememberActions.pre.label;&rememberActions.clearHistory.label;&rememberActions.middle.label;&rememberActions.removeCookies.label;&rememberActions.post.label;
@@ -178,9 +178,9 @@
&dontrememberDescription.label;
- &dontrememberActions.pre.label;&dontrememberActions.post.label;
+ &dontrememberActions.pre.label;&dontrememberActions.clearHistory.label;&dontrememberActions.post.label;
diff --git a/browser/components/sessionstore/SessionWorker.js b/browser/components/sessionstore/SessionWorker.js
index 7b53c6d83f0e..bc3071f73302 100644
--- a/browser/components/sessionstore/SessionWorker.js
+++ b/browser/components/sessionstore/SessionWorker.js
@@ -344,25 +344,29 @@ var Agent = {
let exn = null;
let iterator = new File.DirectoryIterator(path);
- if (!iterator.exists()) {
- return;
- }
- for (let entry in iterator) {
- if (entry.isDir) {
- continue;
+ try {
+ if (!iterator.exists()) {
+ return;
}
- if (!prefix || entry.name.startsWith(prefix)) {
- try {
- File.remove(entry.path);
- } catch (ex) {
- // Don't stop immediately
- exn = exn || ex;
+ for (let entry in iterator) {
+ if (entry.isDir) {
+ continue;
+ }
+ if (!prefix || entry.name.startsWith(prefix)) {
+ try {
+ File.remove(entry.path);
+ } catch (ex) {
+ // Don't stop immediately
+ exn = exn || ex;
+ }
}
}
- }
- if (exn) {
- throw exn;
+ if (exn) {
+ throw exn;
+ }
+ } finally {
+ iterator.close();
}
},
};
diff --git a/browser/config/mozconfigs/linux64/artifact b/browser/config/mozconfigs/linux64/artifact
index fb28e4c5116f..4a1ecafc2ea3 100644
--- a/browser/config/mozconfigs/linux64/artifact
+++ b/browser/config/mozconfigs/linux64/artifact
@@ -1,9 +1,8 @@
MOZ_AUTOMATION_BUILD_SYMBOLS=0
MOZ_AUTOMATION_L10N_CHECK=0
-NO_CACHE=1
-
-. "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+. "$topsrcdir/build/mozconfig.common.override"
ac_add_options --enable-artifact-builds
unset CC
diff --git a/browser/extensions/loop/bootstrap.js b/browser/extensions/loop/bootstrap.js
index 14f532f10c70..1cf8f9146508 100644
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -876,6 +876,25 @@ var WindowListener = {
*/
_browserSharePaused: false,
+ /**
+ * Stores details about the last notification.
+ *
+ * @type {Object}
+ */
+ _lastNotification: {},
+
+ /**
+ * Used to determine if the browser sharing info bar is currently being
+ * shown or not.
+ */
+ _showingBrowserSharingInfoBar: function _showingBrowserSharingInfoBar() {
+ var browser = gBrowser.selectedBrowser;
+ var box = gBrowser.getNotificationBox(browser);
+ var notification = box.getNotificationWithValue(kBrowserSharingNotificationId);
+
+ return !!notification;},
+
+
/**
* Shows an infobar notification at the top of the browser window that warns
* the user that their browser tabs are being broadcasted through the current
@@ -884,10 +903,25 @@ var WindowListener = {
* @return {void}
*/
_maybeShowBrowserSharingInfoBar: function _maybeShowBrowserSharingInfoBar(currentRoomToken) {var _this13 = this;
- this._hideBrowserSharingInfoBar();
-
var participantsCount = this.LoopRooms.getNumParticipants(currentRoomToken);
+ if (this._showingBrowserSharingInfoBar()) {
+ // When we first open the room, there will be one or zero partipicants
+ // in the room. The notification box changes when there's more than one,
+ // so work that out here.
+ var notAlone = participantsCount > 1;
+ var previousNotAlone = this._lastNotification.participantsCount <= 1;
+
+ // If we're not actually changing the notification bar, then don't
+ // re-display it. This avoids the bar sliding in twice.
+ if (notAlone !== previousNotAlone &&
+ this._browserSharePaused === this._lastNotification.paused) {
+ return;}
+
+
+ this._hideBrowserSharingInfoBar();}
+
+
var initStrings = this._setInfoBarStrings(participantsCount > 1, this._browserSharePaused);
var box = gBrowser.getNotificationBox();
@@ -935,7 +969,10 @@ var WindowListener = {
bar.classList.toggle("paused", !!this._browserSharePaused);
// Keep showing the notification bar until the user explicitly closes it.
- bar.persistence = -1;},
+ bar.persistence = -1;
+
+ this._lastNotification.participantsCount = participantsCount;
+ this._lastNotification.paused = this._browserSharePaused;},
/**
diff --git a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
index 440dd92e4e87..89485ecbd366 100644
--- a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
@@ -1105,19 +1105,19 @@ var MozLoopServiceInternal = {
// It's common for unit tests to overload Chat.open, so check if we actually
// got a DOM node back.
} else if (chatboxInstance.setAttribute) {
- // Set properties that influence visual appearance of the chatbox right
- // away to circumvent glitches.
- chatboxInstance.setAttribute("customSize", "loopDefault");
- chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
- var buttons = "minimize,";
- if (MozLoopService.getLoopPref("conversationPopOut.enabled")) {
- buttons += "swap,";}
+ // Set properties that influence visual appearance of the chatbox right
+ // away to circumvent glitches.
+ chatboxInstance.setAttribute("customSize", "loopDefault");
+ chatboxInstance.parentNode.setAttribute("customSize", "loopDefault");
+ var buttons = "minimize,";
+ if (MozLoopService.getLoopPref("conversationPopOut.enabled")) {
+ buttons += "swap,";}
- Chat.loadButtonSet(chatboxInstance, buttons + kChatboxHangupButton.id);
- // Final fall-through in case a unit test overloaded Chat.open. Here we can
- // immediately resolve the promise.
- } else {
- resolve(windowId);}});},
+ Chat.loadButtonSet(chatboxInstance, buttons + kChatboxHangupButton.id);
+ // Final fall-through in case a unit test overloaded Chat.open. Here we can
+ // immediately resolve the promise.
+ } else {
+ resolve(windowId);}});},
diff --git a/browser/extensions/loop/chrome/content/panels/js/roomViews.js b/browser/extensions/loop/chrome/content/panels/js/roomViews.js
index eaa3e179229d..973481001382 100644
--- a/browser/extensions/loop/chrome/content/panels/js/roomViews.js
+++ b/browser/extensions/loop/chrome/content/panels/js/roomViews.js
@@ -185,20 +185,11 @@ loop.roomViews = function (mozL10n) {
nextState.roomState === ROOM_STATES.MEDIA_WAIT) {
this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
publisherConfig: this.getDefaultPublisherConfig({
- publishVideo: !this.state.videoMuted }) }));}
+ publishVideo: !this.state.videoMuted }) }));}},
- // Now that we're ready to share, automatically start sharing a tab only
- // if we're not already connected to the room via the sdk, e.g. not in the
- // case a remote participant just left.
- if (nextState.roomState === ROOM_STATES.SESSION_CONNECTED &&
- !(this.state.roomState === ROOM_STATES.SESSION_CONNECTED ||
- this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS)) {
- this.props.dispatcher.dispatch(new sharedActions.StartBrowserShare());}},
-
-
/**
* User clicked on the "Leave" button.
diff --git a/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js b/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
index 4aa040d7fa90..4e3c42d87aea 100644
--- a/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/roomViews_test.js
@@ -370,44 +370,11 @@ describe("loop.roomViews", function () {
sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.SetupStreamElements({
publisherConfig: {
- fake: "config" } }));});
+ fake: "config" } }));});});
- it("should dispatch a `StartBrowserShare` action when the SESSION_CONNECTED state is entered", function () {
- activeRoomStore.setStoreState({ roomState: ROOM_STATES.READY });
- mountTestComponent();
-
- activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
-
- sinon.assert.calledOnce(dispatcher.dispatch);
- sinon.assert.calledWithExactly(dispatcher.dispatch,
- new sharedActions.StartBrowserShare());});
-
-
- it("should not dispatch a `StartBrowserShare` action when the previous state was HAS_PARTICIPANTS", function () {
- activeRoomStore.setStoreState({ roomState: ROOM_STATES.HAS_PARTICIPANTS });
- mountTestComponent();
-
- activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
-
- sinon.assert.notCalled(dispatcher.dispatch);});
-
-
- it("should not dispatch a `StartBrowserShare` action when the previous state was SESSION_CONNECTED", function () {
- activeRoomStore.setStoreState({ roomState: ROOM_STATES.SESSION_CONNECTED });
- mountTestComponent();
-
- activeRoomStore.setStoreState({
- roomState: ROOM_STATES.SESSION_CONNECTED,
- // Additional change to force an update.
- screenSharingState: "fake" });
-
-
- sinon.assert.notCalled(dispatcher.dispatch);});});
-
-
describe("#render", function () {
it("should set document.title to store.serverData.roomName", function () {
diff --git a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
index f175baf3360e..98d391d0d981 100644
--- a/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
+++ b/browser/extensions/loop/chrome/content/shared/js/activeRoomStore.js
@@ -718,8 +718,16 @@ loop.store.ActiveRoomStore = function (mozL10n) {
this._sdkDriver.connectSession(actionData);
- loop.request("AddConversationContext", this._storeState.windowId,
- actionData.sessionId, "");},
+ this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
+
+ // Set up a listener for watching screen shares. This will get notified
+ // with the first windowId when it is added, so we may start off the sharing
+ // from within the listener.
+ loop.subscribe("BrowserSwitch", this._browserSharingListener);
+
+ loop.requestMulti(["AddConversationContext", this._storeState.windowId,
+ actionData.sessionId, ""],
+ ["AddBrowserSharingListener", this.getStoreState().windowId]);},
/**
@@ -780,7 +788,14 @@ loop.store.ActiveRoomStore = function (mozL10n) {
this.setStoreState({
remoteAudioEnabled: actionData.hasAudio,
remoteVideoEnabled: actionData.hasVideo,
- remoteSrcMediaElement: actionData.srcMediaElement });},
+ remoteSrcMediaElement: actionData.srcMediaElement });
+
+
+ // We start browser sharing here so that it starts *after* the audio/video
+ // has connected. This is to attempt to help performance when a room is
+ // initially joined.
+ if (this._isDesktop) {
+ this.startBrowserShare();}},
@@ -866,10 +881,9 @@ loop.store.ActiveRoomStore = function (mozL10n) {
if (Array.isArray(windowId)) {
windowId = windowId[0];}
- if (!windowId) {
- return;}
- if (windowId.isError) {
+ // There was an error getting the windowId, so lets just reset things.
+ if (windowId && windowId.isError) {
console.error("Error getting the windowId: " + windowId.message);
this.dispatchAction(new sharedActions.ScreenSharingState({
state: SCREEN_SHARE_STATES.INACTIVE }));
@@ -877,8 +891,27 @@ loop.store.ActiveRoomStore = function (mozL10n) {
return;}
+ // If there's no windowId, see if we've got one saved.
+ if (!windowId) {
+ // If we really don't have a window Id, then don't do anything.
+ if (!this._savedWindowId) {
+ return;}
+
+
+ windowId = this._savedWindowId;
+ delete this._savedWindowId;}
+
+
var screenSharingState = this.getStoreState().screenSharingState;
+ // If we're inactive, or screen sharing is paused, just save the windowId
+ // for when we're ready.
+ if (screenSharingState === SCREEN_SHARE_STATES.INACTIVE ||
+ this._storeState.sharingPaused) {
+ this._savedWindowId = windowId;
+ return;}
+
+
if (screenSharingState === SCREEN_SHARE_STATES.PENDING) {
// Screen sharing is still pending, so assume that we need to kick it off.
var options = {
@@ -947,14 +980,10 @@ loop.store.ActiveRoomStore = function (mozL10n) {
state: SCREEN_SHARE_STATES.PENDING }));
- this._browserSharingListener = this._handleSwitchBrowserShare.bind(this);
-
- // Set up a listener for watching screen shares. This will get notified
- // with the first windowId when it is added, so we start off the sharing
- // from within the listener.
- loop.request("AddBrowserSharingListener", this.getStoreState().windowId).
- then(this._browserSharingListener);
- loop.subscribe("BrowserSwitch", this._browserSharingListener);},
+ // This attempts to start the actual browser sharing - we assume we've
+ // already got a windowId due to having requested it before connecting the
+ // media.
+ this._handleSwitchBrowserShare();},
/**
@@ -985,6 +1014,16 @@ loop.store.ActiveRoomStore = function (mozL10n) {
sharingPaused: !actionData.enabled });
+ // If we've un-paused screen sharing, but we haven't started sharing, then
+ // we need to start that off.
+ if (actionData.enabled &&
+ this._storeState.screenSharingState === SCREEN_SHARE_STATES.PENDING) {
+ this._handleSwitchBrowserShare();} else
+ {
+ // Otherwise just toggle the stream.
+ this._sdkDriver.toggleBrowserSharing(actionData.enabled);}
+
+
// If unpausing, check the context as it might have changed.
if (actionData.enabled) {
this._checkTabContext();}},
diff --git a/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js b/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
index 94c75c43c3b6..3b35a131c49a 100644
--- a/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
+++ b/browser/extensions/loop/chrome/content/shared/js/otSdkDriver.js
@@ -41,8 +41,7 @@ loop.OTSdkDriver = function () {
this.dispatcher.register(this, [
"setupStreamElements",
- "setMute",
- "toggleBrowserSharing"]);
+ "setMute"]);
// Set loop.debug.sdk to true in the browser, or in standalone:
@@ -237,11 +236,12 @@ loop.OTSdkDriver = function () {
/**
* Paused or resumes an active screenshare session as appropriate.
*
- * @param {sharedActions.ToggleBrowserSharing} actionData The data associated with the
- * action. See action.js.
+ * @param {Boolean} enabled True if browser sharing should be enabled.
*/
- toggleBrowserSharing: function toggleBrowserSharing(actionData) {
- this.screenshare.publishVideo(actionData.enabled);},
+ toggleBrowserSharing: function toggleBrowserSharing(enabled) {
+ if (this.screenshare) {
+ this.screenshare.publishVideo(enabled);}},
+
/**
@@ -537,10 +537,8 @@ loop.OTSdkDriver = function () {
_handleRemoteScreenShareCreated: function _handleRemoteScreenShareCreated(stream) {
// Let the stores know first if the screen sharing is paused or not so
// they can update the display properly
- if (!stream[STREAM_PROPERTIES.HAS_VIDEO]) {
- this.dispatcher.dispatch(new sharedActions.VideoScreenStreamChanged({
- hasVideo: false }));}
-
+ this.dispatcher.dispatch(new sharedActions.VideoScreenStreamChanged({
+ hasVideo: stream[STREAM_PROPERTIES.HAS_VIDEO] }));
// Let the stores know so they can update the display if needed.
diff --git a/browser/extensions/loop/chrome/content/shared/js/utils.js b/browser/extensions/loop/chrome/content/shared/js/utils.js
index 89206e953d71..009787cb8966 100644
--- a/browser/extensions/loop/chrome/content/shared/js/utils.js
+++ b/browser/extensions/loop/chrome/content/shared/js/utils.js
@@ -361,17 +361,17 @@ if (inChrome) {
// MediaStreamTrack is the older version of the API, implemented originally
// by Google Chrome.
} else if ("MediaStreamTrack" in rootObject &&
- "getSources" in rootObject.MediaStreamTrack) {
- rootObject.MediaStreamTrack.getSources(function (result) {
- function checkForInput(device) {
- return device.kind === "audio" || device.kind === "video";}
+ "getSources" in rootObject.MediaStreamTrack) {
+ rootObject.MediaStreamTrack.getSources(function (result) {
+ function checkForInput(device) {
+ return device.kind === "audio" || device.kind === "video";}
- callback(result.some(checkForInput));});} else
+ callback(result.some(checkForInput));});} else
- {
- // We don't know, so assume true.
- callback(true);}}
+ {
+ // We don't know, so assume true.
+ callback(true);}}
diff --git a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
index 07d41c1918b5..1ff05d192ee5 100644
--- a/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
+++ b/browser/extensions/loop/chrome/content/shared/test/activeRoomStore_test.js
@@ -51,6 +51,7 @@ describe("loop.store.ActiveRoomStore", function () {
retryPublishWithoutVideo: sinon.stub(),
startScreenShare: sinon.stub(),
switchAcquiredWindow: sinon.stub(),
+ toggleBrowserSharing: sinon.stub(),
endScreenShare: sinon.stub().returns(true) };
@@ -195,7 +196,12 @@ describe("loop.store.ActiveRoomStore", function () {
sandbox.stub(loop, "unsubscribe");
// Setup the listener.
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store.joinedRoom(new sharedActions.JoinedRoom({
+ apiKey: "",
+ sessionToken: "",
+ sessionId: "",
+ expires: 0 }));
+
// Now simulate room failure.
store.roomFailure(new sharedActions.RoomFailure({
@@ -1206,7 +1212,12 @@ describe("loop.store.ActiveRoomStore", function () {
sandbox.stub(loop, "unsubscribe");
// Setup the listener.
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store.joinedRoom(new sharedActions.JoinedRoom({
+ apiKey: "",
+ sessionToken: "",
+ sessionId: "",
+ expires: 0 }));
+
// Now simulate connection failure.
store.connectionFailure(connectionFailureAction);
@@ -1337,7 +1348,25 @@ describe("loop.store.ActiveRoomStore", function () {
expect(store.getStoreState().localVideoEnabled).eql(false);
expect(store.getStoreState().remoteVideoEnabled).eql(true);
- expect(store.getStoreState().remoteAudioEnabled).eql(true);});});
+ expect(store.getStoreState().remoteAudioEnabled).eql(true);});
+
+
+ it("should call startBrowserShare when is desktop", function () {
+ sandbox.stub(store, "startBrowserShare");
+ store._isDesktop = true;
+ store.setStoreState({
+ localVideoEnabled: false,
+ remoteVideoEnabled: false });
+
+
+ store.mediaStreamCreated(new sharedActions.MediaStreamCreated({
+ hasAudio: true,
+ hasVideo: true,
+ isLocal: false,
+ srcMediaElement: fakeStreamElement }));
+
+
+ sinon.assert.calledOnce(store.startBrowserShare);});});
@@ -1494,18 +1523,7 @@ describe("loop.store.ActiveRoomStore", function () {
describe("#startBrowserShare", function () {
- var getSelectedTabMetadataStub;
-
beforeEach(function () {
- getSelectedTabMetadataStub = sinon.stub();
- LoopMochaUtils.stubLoopRequest({
- GetSelectedTabMetadata: getSelectedTabMetadataStub.returns({
- title: "fakeTitle",
- favicon: "fakeFavicon",
- url: "http://www.fakeurl.com" }) });
-
-
-
store.setStoreState({
roomState: ROOM_STATES.JOINED,
roomToken: "fakeToken",
@@ -1519,7 +1537,8 @@ describe("loop.store.ActiveRoomStore", function () {
- sandbox.stub(console, "error");});
+ sandbox.stub(console, "error");
+ sandbox.stub(store, "_handleSwitchBrowserShare");});
afterEach(function () {
@@ -1556,13 +1575,140 @@ describe("loop.store.ActiveRoomStore", function () {
- it("should add a browser sharing listener for tab sharing", function () {
+ it("should call _handleSwitchBrowserShare", function () {
store.startBrowserShare(new sharedActions.StartBrowserShare());
- sinon.assert.calledOnce(requestStubs.AddBrowserSharingListener);});
+
+ sinon.assert.calledOnce(store._handleSwitchBrowserShare);});});
- it("should invoke the SDK driver with the correct options for tab sharing", function () {
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+
+ describe("Screen share Events", function () {
+ it("should call _handleSwitchBrowserShare", function () {
+ sandbox.stub(store, "_handleSwitchBrowserShare");
+
+ store.joinedRoom(new sharedActions.JoinedRoom({
+ apiKey: "",
+ sessionToken: "",
+ sessionId: "",
+ expires: 0 }));
+
+
+ LoopMochaUtils.publish("BrowserSwitch", 72);
+
+ sinon.assert.calledOnce(store._handleSwitchBrowserShare);});});
+
+
+
+ describe("#_handleSwitchBrowserShare", function () {
+ var getSelectedTabMetadataStub;
+
+ beforeEach(function () {
+ getSelectedTabMetadataStub = sinon.stub();
+ LoopMochaUtils.stubLoopRequest({
+ GetSelectedTabMetadata: getSelectedTabMetadataStub.returns({
+ title: "fakeTitle",
+ favicon: "fakeFavicon",
+ url: "http://www.fakeurl.com" }) });
+
+
+
+ store.setStoreState({
+ roomState: ROOM_STATES.JOINED,
+ roomToken: "fakeToken",
+ screenSharingState: SCREEN_SHARE_STATES.ACTIVE,
+ sessionToken: "1627384950",
+ participants: [{
+ displayName: "Owner",
+ owner: true },
+ {
+ displayName: "Guest",
+ owner: false }] });
+
+
+
+ // Stub to prevent errors surfacing in the console.
+ sandbox.stub(console, "error");});
+
+
+ afterEach(function () {
+ store.endScreenShare();});
+
+
+ it("should log an error in the console", function () {
+ var err = new Error("foo");
+ err.isError = true;
+ store._handleSwitchBrowserShare(err);
+
+ sinon.assert.calledOnce(console.error);});
+
+
+ it("should end the screen sharing session when the listener receives an error", function () {
+ var err = new Error("foo");
+ err.isError = true;
+ store._handleSwitchBrowserShare(err);
+
+ // The dispatcher was already called once in beforeEach().
+ sinon.assert.calledOnce(dispatcher.dispatch);
+ sinon.assert.calledWith(dispatcher.dispatch,
+ new sharedActions.ScreenSharingState({
+ state: SCREEN_SHARE_STATES.INACTIVE }));
+
+ sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});
+
+
+ it("should save the windowId when the state is INACTIVE", function () {
+ store.setStoreState({
+ screenSharingState: SCREEN_SHARE_STATES.INACTIVE });
+
+
+ store._handleSwitchBrowserShare(72);
+
+ expect(store._savedWindowId, 72);});
+
+
+ it("should not do anything else when the state is INACTIVE", function () {
+ store.setStoreState({
+ screenSharingState: SCREEN_SHARE_STATES.INACTIVE });
+
+
+ store._handleSwitchBrowserShare(72);
+
+ sinon.assert.notCalled(dispatcher.dispatch);
+ sinon.assert.notCalled(fakeSdkDriver.startScreenShare);
+ sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});
+
+
+ it("should save the windowId when sharing is paused", function () {
+ store.setStoreState({
+ screenSharingState: SCREEN_SHARE_STATES.ACTIVE,
+ sharingPaused: true });
+
+
+ store._handleSwitchBrowserShare(72);
+
+ expect(store._savedWindowId, 72);});
+
+
+ it("should not do anything else when the state is paused", function () {
+ store.setStoreState({
+ screenSharingState: SCREEN_SHARE_STATES.ACTIVE,
+ sharingPaused: true });
+
+
+ store._handleSwitchBrowserShare(72);
+
+ sinon.assert.notCalled(dispatcher.dispatch);
+ sinon.assert.notCalled(fakeSdkDriver.startScreenShare);
+ sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});
+
+
+ it("should invoke the SDK driver with the correct options if the state is pending", function () {
+ store.setStoreState({
+ screenSharingState: SCREEN_SHARE_STATES.PENDING });
+
+
+ store._handleSwitchBrowserShare(42);
+
sinon.assert.calledOnce(fakeSdkDriver.startScreenShare);
sinon.assert.calledWith(fakeSdkDriver.startScreenShare, {
videoSource: "browser",
@@ -1573,13 +1719,31 @@ describe("loop.store.ActiveRoomStore", function () {
+ it("should update the SDK driver when the state is active", function () {
+ store._handleSwitchBrowserShare(72);
+
+ sinon.assert.calledOnce(fakeSdkDriver.switchAcquiredWindow);
+ sinon.assert.calledWithExactly(fakeSdkDriver.switchAcquiredWindow, 72);});
+
+
+ it("should log an error if the state is unexpected", function () {
+ store.setStoreState({
+ screenSharingState: "invalid" });
+
+
+ store._handleSwitchBrowserShare(72);
+
+ sinon.assert.calledOnce(console.error);});
+
+
it("should request the new metadata when the browser being shared change", function () {
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store._handleSwitchBrowserShare(42);
+
clock.tick(500);
sinon.assert.calledOnce(getSelectedTabMetadataStub);
- sinon.assert.calledTwice(dispatcher.dispatch);
- sinon.assert.calledWith(dispatcher.dispatch.getCall(1),
+ sinon.assert.calledOnce(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomContext({
newRoomDescription: "fakeTitle",
newRoomThumbnail: "fakeFavicon",
@@ -1591,13 +1755,14 @@ describe("loop.store.ActiveRoomStore", function () {
it("should process only one request", function () {
store.startBrowserShare(new sharedActions.StartBrowserShare());
// Simulates multiple requests.
- LoopMochaUtils.publish("BrowserSwitch", 72);
- LoopMochaUtils.publish("BrowserSwitch", 72);
+ store._handleSwitchBrowserShare(42);
+ store._handleSwitchBrowserShare(42);
clock.tick(500);
- sinon.assert.calledThrice(getSelectedTabMetadataStub);
- sinon.assert.calledTwice(dispatcher.dispatch);
- sinon.assert.calledWith(dispatcher.dispatch.getCall(1),
+
+ sinon.assert.calledTwice(getSelectedTabMetadataStub);
+ sinon.assert.calledOnce(dispatcher.dispatch);
+ sinon.assert.calledWithExactly(dispatcher.dispatch,
new sharedActions.UpdateRoomContext({
newRoomDescription: "fakeTitle",
newRoomThumbnail: "fakeFavicon",
@@ -1612,11 +1777,12 @@ describe("loop.store.ActiveRoomStore", function () {
favicon: "fakeFavicon" });
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store._handleSwitchBrowserShare(42);
+
clock.tick(500);
sinon.assert.calledOnce(getSelectedTabMetadataStub);
- sinon.assert.calledOnce(dispatcher.dispatch);});
+ sinon.assert.notCalled(dispatcher.dispatch);});
it("should not process a request if sharing is paused", function () {
@@ -1624,11 +1790,11 @@ describe("loop.store.ActiveRoomStore", function () {
sharingPaused: true });
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store._handleSwitchBrowserShare(42);
clock.tick(500);
sinon.assert.notCalled(getSelectedTabMetadataStub);
- sinon.assert.calledOnce(dispatcher.dispatch);});
+ sinon.assert.notCalled(dispatcher.dispatch);});
it("should not process a request if no-one is in the room", function () {
@@ -1639,57 +1805,12 @@ describe("loop.store.ActiveRoomStore", function () {
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store._handleSwitchBrowserShare(42);
+
clock.tick(500);
sinon.assert.notCalled(getSelectedTabMetadataStub);
- sinon.assert.calledOnce(dispatcher.dispatch);});});
-
-
-
- describe("Screen share Events", function () {
- beforeEach(function () {
- store.startBrowserShare(new sharedActions.StartBrowserShare());
-
- store.setStoreState({
- screenSharingState: SCREEN_SHARE_STATES.ACTIVE });
-
-
- // Stub to prevent errors surfacing in the console.
- sandbox.stub(window.console, "error");});
-
-
- afterEach(function () {
- store.endScreenShare();});
-
-
- it("should log an error in the console", function () {
- var err = new Error("foo");
- err.isError = true;
- LoopMochaUtils.publish("BrowserSwitch", err);
-
- sinon.assert.calledOnce(console.error);});
-
-
- it("should update the SDK driver when a new window id is received", function () {
- LoopMochaUtils.publish("BrowserSwitch", 72);
-
- sinon.assert.calledOnce(fakeSdkDriver.switchAcquiredWindow);
- sinon.assert.calledWithExactly(fakeSdkDriver.switchAcquiredWindow, 72);});
-
-
- it("should end the screen sharing session when the listener receives an error", function () {
- var err = new Error("foo");
- err.isError = true;
- LoopMochaUtils.publish("BrowserSwitch", err);
-
- // The dispatcher was already called once in beforeEach().
- sinon.assert.calledTwice(dispatcher.dispatch);
- sinon.assert.calledWith(dispatcher.dispatch,
- new sharedActions.ScreenSharingState({
- state: SCREEN_SHARE_STATES.INACTIVE }));
-
- sinon.assert.notCalled(fakeSdkDriver.switchAcquiredWindow);});});
+ sinon.assert.notCalled(dispatcher.dispatch);});});
@@ -1706,7 +1827,12 @@ describe("loop.store.ActiveRoomStore", function () {
it("should remove the sharing listener", function () {
// Setup the listener.
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store.joinedRoom(new sharedActions.JoinedRoom({
+ apiKey: "",
+ sessionToken: "",
+ sessionId: "",
+ expires: 0 }));
+
// Now stop the screen share.
store.endScreenShare();
@@ -1923,7 +2049,12 @@ describe("loop.store.ActiveRoomStore", function () {
sandbox.stub(loop, "unsubscribe");
// Setup the listener.
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store.joinedRoom(new sharedActions.JoinedRoom({
+ apiKey: "",
+ sessionToken: "",
+ sessionId: "",
+ expires: 0 }));
+
// Now unload the window.
store.windowUnload();
@@ -1992,7 +2123,12 @@ describe("loop.store.ActiveRoomStore", function () {
sandbox.stub(loop, "unsubscribe");
// Setup the listener.
- store.startBrowserShare(new sharedActions.StartBrowserShare());
+ store.joinedRoom(new sharedActions.JoinedRoom({
+ apiKey: "",
+ sessionToken: "",
+ sessionId: "",
+ expires: 0 }));
+
// Now leave the room.
store.leaveRoom();
diff --git a/browser/extensions/loop/chrome/content/shared/vendor/sdk.js b/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
index 64f7df02ff2d..eb8d8a004d86 100755
--- a/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
+++ b/browser/extensions/loop/chrome/content/shared/vendor/sdk.js
@@ -1,11 +1,11 @@
/**
- * @license OpenTok.js v2.7.5 ccd6792 HEAD
+ * @license OpenTok.js v2.7.6 cff9122 HEAD
*
* Copyright (c) 2010-2015 TokBox, Inc.
* Released under the MIT license
* http://opensource.org/licenses/MIT
*
- * Date: March 18 10:46:23 2016
+ * Date: June 24 07:05:09 2016
*/
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o
loop@mozilla.org
true
- 1.4.1
+ 1.4.2
2
+
+
+
+ Test for diabled SRI require-sri-for CSP directive
+
+
+
+
+Mozilla Bug 1265318
+
+
+
+
diff --git a/dom/storage/DOMStorage.cpp b/dom/storage/DOMStorage.cpp
index b4f2acb70b37..68116d5d4481 100644
--- a/dom/storage/DOMStorage.cpp
+++ b/dom/storage/DOMStorage.cpp
@@ -112,13 +112,6 @@ DOMStorage::SetItem(const nsAString& aKey, const nsAString& aData,
return;
}
- Telemetry::Accumulate(GetType() == LocalStorage
- ? Telemetry::LOCALDOMSTORAGE_KEY_SIZE_BYTES
- : Telemetry::SESSIONDOMSTORAGE_KEY_SIZE_BYTES, aKey.Length());
- Telemetry::Accumulate(GetType() == LocalStorage
- ? Telemetry::LOCALDOMSTORAGE_VALUE_SIZE_BYTES
- : Telemetry::SESSIONDOMSTORAGE_VALUE_SIZE_BYTES, aData.Length());
-
nsString data;
bool ok = data.Assign(aData, fallible);
if (!ok) {
diff --git a/dom/storage/DOMStorageDBThread.cpp b/dom/storage/DOMStorageDBThread.cpp
index 95e461c9f6fe..16a4bd0f344b 100644
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -489,8 +489,6 @@ DOMStorageDBThread::OpenAndUpdateDatabase()
nsresult
DOMStorageDBThread::InitDatabase()
{
- Telemetry::AutoTimer timer;
-
nsresult rv;
// Here we are on the worker thread. This opens the worker connection.
diff --git a/dom/url/URL.cpp b/dom/url/URL.cpp
new file mode 100644
index 000000000000..be15b6cab2b8
--- /dev/null
+++ b/dom/url/URL.cpp
@@ -0,0 +1,1763 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "URL.h"
+
+#include "DOMMediaStream.h"
+#include "mozilla/dom/File.h"
+#include "mozilla/dom/MediaSource.h"
+#include "mozilla/dom/URLBinding.h"
+#include "mozilla/dom/ipc/BlobChild.h"
+#include "mozilla/dom/ipc/nsIRemoteBlob.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "nsContentUtils.h"
+#include "nsEscape.h"
+#include "nsHostObjectProtocolHandler.h"
+#include "nsIIOService.h"
+#include "nsIURL.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+
+namespace mozilla {
+namespace dom {
+
+///////////////////////////////////////////////////////////////////////////////
+// URL for main-thread
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+// The URL implementation for the main-thread
+class URLMainThread final : public URL
+{
+public:
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ URL& aBase, ErrorResult& aRv);
+
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional& aBase, ErrorResult& aRv);
+
+ static already_AddRefed
+ Constructor(nsISupports* aParent, const nsAString& aURL,
+ const nsAString& aBase, ErrorResult& aRv);
+
+ static already_AddRefed
+ Constructor(nsISupports* aParent, const nsAString& aURL, nsIURI* aBase,
+ ErrorResult& aRv);
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ CreateObjectURLInternal(aGlobal, aBlob.Impl(),
+ NS_LITERAL_CSTRING(BLOBURI_SCHEME), aOptions,
+ aResult, aRv);
+ }
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ CreateObjectURLInternal(aGlobal, &aStream,
+ NS_LITERAL_CSTRING(MEDIASTREAMURI_SCHEME), aOptions,
+ aResult, aRv);
+ }
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv);
+
+ static void
+ CreateObjectURLInternal(const GlobalObject& aGlobal, nsISupports* aObject,
+ const nsACString& aScheme,
+ const objectURLOptions& aOptions,
+ nsAString& aResult, ErrorResult& aRv);
+
+ static void
+ RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
+ ErrorResult& aRv);
+
+ URLMainThread(nsISupports* aParent, already_AddRefed aURI)
+ : URL(aParent)
+ , mURI(aURI)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual void
+ GetHref(nsAString& aHref, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHref(const nsAString& aHref, ErrorResult& aRv) override;
+
+ virtual void
+ GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const override;
+
+ virtual void
+ GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const override;
+
+ virtual void
+ SetProtocol(const nsAString& aProtocol, ErrorResult& aRv) override;
+
+ virtual void
+ GetUsername(nsAString& aUsername, ErrorResult& aRv) const override;
+
+ virtual void
+ SetUsername(const nsAString& aUsername, ErrorResult& aRv) override;
+
+ virtual void
+ GetPassword(nsAString& aPassword, ErrorResult& aRv) const override;
+
+ virtual void
+ SetPassword(const nsAString& aPassword, ErrorResult& aRv) override;
+
+ virtual void
+ GetHost(nsAString& aHost, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHost(const nsAString& aHost, ErrorResult& aRv) override;
+
+ virtual void
+ GetHostname(nsAString& aHostname, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHostname(const nsAString& aHostname, ErrorResult& aRv) override;
+
+ virtual void
+ GetPort(nsAString& aPort, ErrorResult& aRv) const override;
+
+ virtual void
+ SetPort(const nsAString& aPort, ErrorResult& aRv) override;
+
+ virtual void
+ GetPathname(nsAString& aPathname, ErrorResult& aRv) const override;
+
+ virtual void
+ SetPathname(const nsAString& aPathname, ErrorResult& aRv) override;
+
+ virtual void
+ GetSearch(nsAString& aSearch, ErrorResult& aRv) const override;
+
+ virtual void
+ GetHash(nsAString& aHost, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHash(const nsAString& aHash, ErrorResult& aRv) override;
+
+ virtual void UpdateURLSearchParams() override;
+
+ virtual void
+ SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv) override;
+
+ nsIURI*
+ GetURI() const
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mURI;
+ }
+
+private:
+ ~URLMainThread()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ nsCOMPtr mURI;
+};
+
+/* static */ already_AddRefed
+URLMainThread::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ URL& aBase, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ URLMainThread& base = static_cast(aBase);
+ return Constructor(aGlobal.GetAsSupports(), aURL, base.GetURI(), aRv);
+}
+
+/* static */ already_AddRefed
+URLMainThread::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional& aBase, ErrorResult& aRv)
+{
+ if (aBase.WasPassed()) {
+ return Constructor(aGlobal.GetAsSupports(), aURL, aBase.Value(), aRv);
+ }
+
+ return Constructor(aGlobal.GetAsSupports(), aURL, nullptr, aRv);
+}
+
+/* static */ already_AddRefed
+URLMainThread::Constructor(nsISupports* aParent, const nsAString& aURL,
+ const nsAString& aBase, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr baseUri;
+ nsresult rv = NS_NewURI(getter_AddRefs(baseUri), aBase, nullptr, nullptr,
+ nsContentUtils::GetIOService());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError(aBase);
+ return nullptr;
+ }
+
+ return Constructor(aParent, aURL, baseUri, aRv);
+}
+
+/* static */ already_AddRefed
+URLMainThread::Constructor(nsISupports* aParent, const nsAString& aURL,
+ nsIURI* aBase, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aURL, nullptr, aBase,
+ nsContentUtils::GetIOService());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.ThrowTypeError(aURL);
+ return nullptr;
+ }
+
+ RefPtr url = new URLMainThread(aParent, uri.forget());
+ return url.forget();
+}
+
+/* static */ void
+URLMainThread::CreateObjectURL(const GlobalObject& aGlobal,
+ MediaSource& aSource,
+ const objectURLOptions& aOptions,
+ nsAString& aResult, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr principal =
+ nsContentUtils::ObjectPrincipal(aGlobal.Get());
+
+ nsAutoCString url;
+ aRv = nsHostObjectProtocolHandler::
+ AddDataEntry(NS_LITERAL_CSTRING(MEDIASOURCEURI_SCHEME),
+ &aSource, principal, url);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ nsCOMPtr revocation = NS_NewRunnableFunction(
+ [url] {
+ nsHostObjectProtocolHandler::RemoveDataEntry(url);
+ });
+
+ nsContentUtils::RunInStableState(revocation.forget());
+
+ CopyASCIItoUTF16(url, aResult);
+}
+
+/* static */ void
+URLMainThread::CreateObjectURLInternal(const GlobalObject& aGlobal,
+ nsISupports* aObject,
+ const nsACString& aScheme,
+ const objectURLOptions& aOptions,
+ nsAString& aResult, ErrorResult& aRv)
+{
+ nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsCOMPtr principal =
+ nsContentUtils::ObjectPrincipal(aGlobal.Get());
+
+ nsAutoCString url;
+ nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(aScheme, aObject,
+ principal, url);
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ global->RegisterHostObjectURI(url);
+ CopyASCIItoUTF16(url, aResult);
+}
+
+/* static */ void
+URLMainThread::RevokeObjectURL(const GlobalObject& aGlobal,
+ const nsAString& aURL, ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports());
+ if (!global) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal.Get());
+
+ NS_LossyConvertUTF16toASCII asciiurl(aURL);
+
+ nsIPrincipal* urlPrincipal =
+ nsHostObjectProtocolHandler::GetDataEntryPrincipal(asciiurl);
+
+ if (urlPrincipal && principal->Subsumes(urlPrincipal)) {
+ nsCOMPtr global = do_QueryInterface(aGlobal.GetAsSupports());
+ global->UnregisterHostObjectURI(asciiurl);
+ nsHostObjectProtocolHandler::RemoveDataEntry(asciiurl);
+ }
+}
+
+void
+URLMainThread::GetHref(nsAString& aHref, ErrorResult& aRv) const
+{
+ aHref.Truncate();
+
+ nsAutoCString href;
+ nsresult rv = mURI->GetSpec(href);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(href, aHref);
+ }
+}
+
+void
+URLMainThread::SetHref(const nsAString& aHref, ErrorResult& aRv)
+{
+ NS_ConvertUTF16toUTF8 href(aHref);
+
+ nsresult rv;
+ nsCOMPtr ioService(do_GetService(NS_IOSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ nsCOMPtr uri;
+ rv = ioService->NewURI(href, nullptr, nullptr, getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ aRv.ThrowTypeError(aHref);
+ return;
+ }
+
+ mURI = uri;
+ UpdateURLSearchParams();
+}
+
+void
+URLMainThread::GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const
+{
+ nsContentUtils::GetUTFOrigin(mURI, aOrigin);
+}
+
+void
+URLMainThread::GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const
+{
+ nsAutoCString protocol;
+ if (NS_SUCCEEDED(mURI->GetScheme(protocol))) {
+ aProtocol.Truncate();
+ }
+
+ CopyASCIItoUTF16(protocol, aProtocol);
+ aProtocol.Append(char16_t(':'));
+}
+
+void
+URLMainThread::SetProtocol(const nsAString& aProtocol, ErrorResult& aRv)
+{
+ nsAString::const_iterator start, end;
+ aProtocol.BeginReading(start);
+ aProtocol.EndReading(end);
+ nsAString::const_iterator iter(start);
+
+ FindCharInReadable(':', iter, end);
+
+ // Changing the protocol of a URL, changes the "nature" of the URI
+ // implementation. In order to do this properly, we have to serialize the
+ // existing URL and reparse it in a new object.
+ nsCOMPtr clone;
+ nsresult rv = mURI->Clone(getter_AddRefs(clone));
+ if (NS_WARN_IF(NS_FAILED(rv)) || !clone) {
+ return;
+ }
+
+ rv = clone->SetScheme(NS_ConvertUTF16toUTF8(Substring(start, iter)));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsAutoCString href;
+ rv = clone->GetSpec(href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ nsCOMPtr uri;
+ rv = NS_NewURI(getter_AddRefs(uri), href);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ mURI = uri;
+}
+
+#define URL_GETTER( value, func ) \
+ value.Truncate(); \
+ nsAutoCString tmp; \
+ nsresult rv = mURI->func(tmp); \
+ if (NS_SUCCEEDED(rv)) { \
+ CopyUTF8toUTF16(tmp, value); \
+ }
+
+void
+URLMainThread::GetUsername(nsAString& aUsername, ErrorResult& aRv) const
+{
+ URL_GETTER(aUsername, GetUsername);
+}
+
+void
+URLMainThread::SetUsername(const nsAString& aUsername, ErrorResult& aRv)
+{
+ mURI->SetUsername(NS_ConvertUTF16toUTF8(aUsername));
+}
+
+void
+URLMainThread::GetPassword(nsAString& aPassword, ErrorResult& aRv) const
+{
+ URL_GETTER(aPassword, GetPassword);
+}
+
+void
+URLMainThread::SetPassword(const nsAString& aPassword, ErrorResult& aRv)
+{
+ mURI->SetPassword(NS_ConvertUTF16toUTF8(aPassword));
+}
+
+void
+URLMainThread::GetHost(nsAString& aHost, ErrorResult& aRv) const
+{
+ URL_GETTER(aHost, GetHostPort);
+}
+
+void
+URLMainThread::SetHost(const nsAString& aHost, ErrorResult& aRv)
+{
+ mURI->SetHostPort(NS_ConvertUTF16toUTF8(aHost));
+}
+
+void
+URLMainThread::UpdateURLSearchParams()
+{
+ if (!mSearchParams) {
+ return;
+ }
+
+ nsAutoCString search;
+ nsCOMPtr url(do_QueryInterface(mURI));
+ if (url) {
+ nsresult rv = url->GetQuery(search);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ search.Truncate();
+ }
+ }
+
+ mSearchParams->ParseInput(search);
+}
+
+void
+URLMainThread::GetHostname(nsAString& aHostname, ErrorResult& aRv) const
+{
+ aHostname.Truncate();
+ nsContentUtils::GetHostOrIPv6WithBrackets(mURI, aHostname);
+}
+
+void
+URLMainThread::SetHostname(const nsAString& aHostname, ErrorResult& aRv)
+{
+ // nsStandardURL returns NS_ERROR_UNEXPECTED for an empty hostname
+ // The return code is silently ignored
+ mURI->SetHost(NS_ConvertUTF16toUTF8(aHostname));
+}
+
+void
+URLMainThread::GetPort(nsAString& aPort, ErrorResult& aRv) const
+{
+ aPort.Truncate();
+
+ int32_t port;
+ nsresult rv = mURI->GetPort(&port);
+ if (NS_SUCCEEDED(rv) && port != -1) {
+ nsAutoString portStr;
+ portStr.AppendInt(port, 10);
+ aPort.Assign(portStr);
+ }
+}
+
+void
+URLMainThread::SetPort(const nsAString& aPort, ErrorResult& aRv)
+{
+ nsresult rv;
+ nsAutoString portStr(aPort);
+ int32_t port = -1;
+
+ // nsIURI uses -1 as default value.
+ if (!portStr.IsEmpty()) {
+ port = portStr.ToInteger(&rv);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+ }
+
+ mURI->SetPort(port);
+}
+
+void
+URLMainThread::GetPathname(nsAString& aPathname, ErrorResult& aRv) const
+{
+ aPathname.Truncate();
+
+ nsCOMPtr url(do_QueryInterface(mURI));
+ if (!url) {
+ nsAutoCString path;
+ nsresult rv = mURI->GetPath(path);
+ if (NS_FAILED(rv)){
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+ return;
+ }
+
+ CopyUTF8toUTF16(path, aPathname);
+ return;
+ }
+
+ nsAutoCString file;
+ nsresult rv = url->GetFilePath(file);
+ if (NS_SUCCEEDED(rv)) {
+ CopyUTF8toUTF16(file, aPathname);
+ }
+}
+
+void
+URLMainThread::SetPathname(const nsAString& aPathname, ErrorResult& aRv)
+{
+ nsCOMPtr url(do_QueryInterface(mURI));
+ if (!url) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname));
+}
+
+void
+URLMainThread::GetSearch(nsAString& aSearch, ErrorResult& aRv) const
+{
+ aSearch.Truncate();
+
+ nsCOMPtr url(do_QueryInterface(mURI));
+ if (!url) {
+ // Do not throw! Not having a valid URI or URL should result in an empty
+ // string.
+ return;
+ }
+
+ nsAutoCString search;
+ nsresult rv = url->GetQuery(search);
+ if (NS_SUCCEEDED(rv) && !search.IsEmpty()) {
+ CopyUTF8toUTF16(NS_LITERAL_CSTRING("?") + search, aSearch);
+ }
+}
+
+void
+URLMainThread::GetHash(nsAString& aHash, ErrorResult& aRv) const
+{
+ aHash.Truncate();
+
+ nsAutoCString ref;
+ nsresult rv = mURI->GetRef(ref);
+ if (NS_SUCCEEDED(rv) && !ref.IsEmpty()) {
+ aHash.Assign(char16_t('#'));
+ if (nsContentUtils::GettersDecodeURLHash()) {
+ NS_UnescapeURL(ref); // XXX may result in random non-ASCII bytes!
+ }
+ AppendUTF8toUTF16(ref, aHash);
+ }
+}
+
+void
+URLMainThread::SetHash(const nsAString& aHash, ErrorResult& aRv)
+{
+ mURI->SetRef(NS_ConvertUTF16toUTF8(aHash));
+}
+
+void
+URLMainThread::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv)
+{
+ nsCOMPtr url(do_QueryInterface(mURI));
+ if (!url) {
+ // Ignore failures to be compatible with NS4.
+ return;
+ }
+
+ url->SetQuery(NS_ConvertUTF16toUTF8(aSearch));
+}
+
+} // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// URL for Workers
+///////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+using namespace workers;
+
+// Proxy class to forward all the requests to a URLMainThread object.
+class URLProxy final
+{
+public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(URLProxy)
+
+ explicit URLProxy(already_AddRefed aURL)
+ : mURL(aURL)
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ URLMainThread* URL()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mURL;
+ }
+
+ nsIURI* URI()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mURL->GetURI();
+ }
+
+ void ReleaseURI()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+ mURL = nullptr;
+ }
+
+private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~URLProxy()
+ {
+ MOZ_ASSERT(!mURL);
+ }
+
+ RefPtr mURL;
+};
+
+// URLWorker implements the URL object in workers.
+class URLWorker final : public URL
+{
+public:
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ URL& aBase, ErrorResult& aRv);
+
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional& aBase, ErrorResult& aRv);
+
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const nsAString& aBase, ErrorResult& aRv);
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ const mozilla::dom::objectURLOptions& aOptions,
+ nsAString& aResult, mozilla::ErrorResult& aRv);
+
+ static void
+ RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aUrl,
+ ErrorResult& aRv);
+
+ URLWorker(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy);
+
+ virtual void
+ GetHref(nsAString& aHref, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHref(const nsAString& aHref, ErrorResult& aRv) override;
+
+ virtual void
+ GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const override;
+
+ virtual void
+ GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const override;
+
+ virtual void
+ SetProtocol(const nsAString& aProtocol, ErrorResult& aRv) override;
+
+ virtual void
+ GetUsername(nsAString& aUsername, ErrorResult& aRv) const override;
+
+ virtual void
+ SetUsername(const nsAString& aUsername, ErrorResult& aRv) override;
+
+ virtual void
+ GetPassword(nsAString& aPassword, ErrorResult& aRv) const override;
+
+ virtual void
+ SetPassword(const nsAString& aPassword, ErrorResult& aRv) override;
+
+ virtual void
+ GetHost(nsAString& aHost, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHost(const nsAString& aHost, ErrorResult& aRv) override;
+
+ virtual void
+ GetHostname(nsAString& aHostname, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHostname(const nsAString& aHostname, ErrorResult& aRv) override;
+
+ virtual void
+ GetPort(nsAString& aPort, ErrorResult& aRv) const override;
+
+ virtual void
+ SetPort(const nsAString& aPort, ErrorResult& aRv) override;
+
+ virtual void
+ GetPathname(nsAString& aPathname, ErrorResult& aRv) const override;
+
+ virtual void
+ SetPathname(const nsAString& aPathname, ErrorResult& aRv) override;
+
+ virtual void
+ GetSearch(nsAString& aSearch, ErrorResult& aRv) const override;
+
+ virtual void
+ GetHash(nsAString& aHost, ErrorResult& aRv) const override;
+
+ virtual void
+ SetHash(const nsAString& aHash, ErrorResult& aRv) override;
+
+ virtual void UpdateURLSearchParams() override;
+
+ virtual void
+ SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv) override;
+
+ URLProxy*
+ GetURLProxy() const
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ return mURLProxy;
+ }
+
+private:
+ ~URLWorker();
+
+ workers::WorkerPrivate* mWorkerPrivate;
+ RefPtr mURLProxy;
+};
+
+// This class creates an URL from a DOM Blob on the main thread.
+class CreateURLRunnable : public WorkerMainThreadRunnable
+{
+private:
+ BlobImpl* mBlobImpl;
+ nsAString& mURL;
+
+public:
+ CreateURLRunnable(WorkerPrivate* aWorkerPrivate, BlobImpl* aBlobImpl,
+ const objectURLOptions& aOptions,
+ nsAString& aURL)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("URL :: CreateURL"))
+ , mBlobImpl(aBlobImpl)
+ , mURL(aURL)
+ {
+ MOZ_ASSERT(aBlobImpl);
+
+ DebugOnly isMutable;
+ MOZ_ASSERT(NS_SUCCEEDED(aBlobImpl->GetMutable(&isMutable)));
+ MOZ_ASSERT(!isMutable);
+ }
+
+ bool
+ MainThreadRun()
+ {
+ using namespace mozilla::ipc;
+
+ AssertIsOnMainThread();
+
+ RefPtr newBlobImplHolder;
+
+ if (nsCOMPtr remoteBlob = do_QueryInterface(mBlobImpl)) {
+ if (BlobChild* blobChild = remoteBlob->GetBlobChild()) {
+ if (PBackgroundChild* blobManager = blobChild->GetBackgroundManager()) {
+ PBackgroundChild* backgroundManager =
+ BackgroundChild::GetForCurrentThread();
+ MOZ_ASSERT(backgroundManager);
+
+ if (blobManager != backgroundManager) {
+ // Always make sure we have a blob from an actor we can use on this
+ // thread.
+ blobChild = BlobChild::GetOrCreate(backgroundManager, mBlobImpl);
+ MOZ_ASSERT(blobChild);
+
+ newBlobImplHolder = blobChild->GetBlobImpl();
+ MOZ_ASSERT(newBlobImplHolder);
+
+ mBlobImpl = newBlobImplHolder;
+ }
+ }
+ }
+ }
+
+ DebugOnly isMutable;
+ MOZ_ASSERT(NS_SUCCEEDED(mBlobImpl->GetMutable(&isMutable)));
+ MOZ_ASSERT(!isMutable);
+
+ nsCOMPtr principal = mWorkerPrivate->GetPrincipal();
+
+ nsAutoCString url;
+ nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(
+ NS_LITERAL_CSTRING(BLOBURI_SCHEME),
+ mBlobImpl, principal, url);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to add data entry for the blob!");
+ SetDOMStringToNull(mURL);
+ return false;
+ }
+
+ if (!mWorkerPrivate->IsSharedWorker() &&
+ !mWorkerPrivate->IsServiceWorker()) {
+ // Walk up to top worker object.
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (WorkerPrivate* parent = wp->GetParent()) {
+ wp = parent;
+ }
+
+ nsCOMPtr sc = wp->GetScriptContext();
+ // We could not have a ScriptContext in JSM code. In this case, we leak.
+ if (sc) {
+ nsCOMPtr global = sc->GetGlobalObject();
+ MOZ_ASSERT(global);
+
+ global->RegisterHostObjectURI(url);
+ }
+ }
+
+ mURL = NS_ConvertUTF8toUTF16(url);
+ return true;
+ }
+};
+
+// This class revokes an URL on the main thread.
+class RevokeURLRunnable : public WorkerMainThreadRunnable
+{
+private:
+ const nsString mURL;
+
+public:
+ RevokeURLRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aURL)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("URL :: RevokeURL"))
+ , mURL(aURL)
+ {}
+
+ bool
+ MainThreadRun()
+ {
+ AssertIsOnMainThread();
+
+ NS_ConvertUTF16toUTF8 url(mURL);
+
+ nsIPrincipal* urlPrincipal =
+ nsHostObjectProtocolHandler::GetDataEntryPrincipal(url);
+
+ nsCOMPtr principal = mWorkerPrivate->GetPrincipal();
+
+ bool subsumes;
+ if (urlPrincipal &&
+ NS_SUCCEEDED(principal->Subsumes(urlPrincipal, &subsumes)) &&
+ subsumes) {
+ nsHostObjectProtocolHandler::RemoveDataEntry(url);
+ }
+
+ if (!mWorkerPrivate->IsSharedWorker() &&
+ !mWorkerPrivate->IsServiceWorker()) {
+ // Walk up to top worker object.
+ WorkerPrivate* wp = mWorkerPrivate;
+ while (WorkerPrivate* parent = wp->GetParent()) {
+ wp = parent;
+ }
+
+ nsCOMPtr sc = wp->GetScriptContext();
+ // We could not have a ScriptContext in JSM code. In this case, we leak.
+ if (sc) {
+ nsCOMPtr global = sc->GetGlobalObject();
+ MOZ_ASSERT(global);
+
+ global->UnregisterHostObjectURI(url);
+ }
+ }
+
+ return true;
+ }
+};
+
+// This class creates a URL object on the main thread.
+class ConstructorRunnable : public WorkerMainThreadRunnable
+{
+private:
+ const nsString mURL;
+
+ nsString mBase; // IsVoid() if we have no base URI string.
+ RefPtr mBaseProxy;
+ ErrorResult& mRv;
+
+ RefPtr mRetval;
+
+public:
+ ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aURL, const Optional& aBase,
+ ErrorResult& aRv)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("URL :: Constructor"))
+ , mURL(aURL)
+ , mRv(aRv)
+ {
+ if (aBase.WasPassed()) {
+ mBase = aBase.Value();
+ } else {
+ mBase.SetIsVoid(true);
+ }
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ ConstructorRunnable(WorkerPrivate* aWorkerPrivate,
+ const nsAString& aURL, URLProxy* aBaseProxy,
+ ErrorResult& aRv)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ NS_LITERAL_CSTRING("URL :: Constructor with BaseURL"))
+ , mURL(aURL)
+ , mBaseProxy(aBaseProxy)
+ , mRv(aRv)
+ {
+ mBase.SetIsVoid(true);
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool
+ MainThreadRun()
+ {
+ AssertIsOnMainThread();
+
+ RefPtr url;
+ if (mBaseProxy) {
+ url = URLMainThread::Constructor(nullptr, mURL, mBaseProxy->URI(), mRv);
+ } else if (!mBase.IsVoid()) {
+ url = URLMainThread::Constructor(nullptr, mURL, mBase, mRv);
+ } else {
+ url = URLMainThread::Constructor(nullptr, mURL, nullptr, mRv);
+ }
+
+ if (mRv.Failed()) {
+ return true;
+ }
+
+ mRetval = new URLProxy(url.forget());
+ return true;
+ }
+
+ URLProxy*
+ GetURLProxy()
+ {
+ return mRetval;
+ }
+};
+
+class TeardownURLRunnable : public Runnable
+{
+public:
+ explicit TeardownURLRunnable(URLProxy* aURLProxy)
+ : mURLProxy(aURLProxy)
+ {
+ }
+
+ NS_IMETHOD Run()
+ {
+ AssertIsOnMainThread();
+
+ mURLProxy->ReleaseURI();
+ mURLProxy = nullptr;
+
+ return NS_OK;
+ }
+
+private:
+ RefPtr mURLProxy;
+};
+
+// This class is the generic getter for any URL property.
+class GetterRunnable : public WorkerMainThreadRunnable
+{
+public:
+ enum GetterType {
+ GetterHref,
+ GetterOrigin,
+ GetterProtocol,
+ GetterUsername,
+ GetterPassword,
+ GetterHost,
+ GetterHostname,
+ GetterPort,
+ GetterPathname,
+ GetterSearch,
+ GetterHash,
+ };
+
+ GetterRunnable(WorkerPrivate* aWorkerPrivate,
+ GetterType aType, nsAString& aValue,
+ URLProxy* aURLProxy)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ // We can have telemetry keys for each getter when
+ // needed.
+ NS_LITERAL_CSTRING("URL :: getter"))
+ , mValue(aValue)
+ , mType(aType)
+ , mURLProxy(aURLProxy)
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool
+ MainThreadRun()
+ {
+ AssertIsOnMainThread();
+ ErrorResult rv;
+
+ switch (mType) {
+ case GetterHref:
+ mURLProxy->URL()->GetHref(mValue, rv);
+ break;
+
+ case GetterOrigin:
+ mURLProxy->URL()->GetOrigin(mValue, rv);
+ break;
+
+ case GetterProtocol:
+ mURLProxy->URL()->GetProtocol(mValue, rv);
+ break;
+
+ case GetterUsername:
+ mURLProxy->URL()->GetUsername(mValue, rv);
+ break;
+
+ case GetterPassword:
+ mURLProxy->URL()->GetPassword(mValue, rv);
+ break;
+
+ case GetterHost:
+ mURLProxy->URL()->GetHost(mValue, rv);
+ break;
+
+ case GetterHostname:
+ mURLProxy->URL()->GetHostname(mValue, rv);
+ break;
+
+ case GetterPort:
+ mURLProxy->URL()->GetPort(mValue, rv);
+ break;
+
+ case GetterPathname:
+ mURLProxy->URL()->GetPathname(mValue, rv);
+ break;
+
+ case GetterSearch:
+ mURLProxy->URL()->GetSearch(mValue, rv);
+ break;
+
+ case GetterHash:
+ mURLProxy->URL()->GetHash(mValue, rv);
+ break;
+ }
+
+ MOZ_ASSERT(!rv.Failed(), "Main-thread getters do not fail.");
+ return true;
+ }
+
+private:
+ nsAString& mValue;
+ GetterType mType;
+ RefPtr mURLProxy;
+};
+
+// This class is the generic setter for any URL property.
+class SetterRunnable : public WorkerMainThreadRunnable
+{
+public:
+ enum SetterType {
+ SetterHref,
+ SetterProtocol,
+ SetterUsername,
+ SetterPassword,
+ SetterHost,
+ SetterHostname,
+ SetterPort,
+ SetterPathname,
+ SetterSearch,
+ SetterHash,
+ };
+
+ SetterRunnable(WorkerPrivate* aWorkerPrivate,
+ SetterType aType, const nsAString& aValue,
+ URLProxy* aURLProxy)
+ : WorkerMainThreadRunnable(aWorkerPrivate,
+ // We can have telemetry keys for each setter when
+ // needed.
+ NS_LITERAL_CSTRING("URL :: setter"))
+ , mValue(aValue)
+ , mType(aType)
+ , mURLProxy(aURLProxy)
+ , mFailed(false)
+ {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ bool
+ MainThreadRun()
+ {
+ AssertIsOnMainThread();
+ ErrorResult rv;
+
+ switch (mType) {
+ case SetterHref: {
+ mURLProxy->URL()->SetHref(mValue, rv);
+ break;
+ }
+
+ case SetterProtocol:
+ mURLProxy->URL()->SetProtocol(mValue, rv);
+ break;
+
+ case SetterUsername:
+ mURLProxy->URL()->SetUsername(mValue, rv);
+ break;
+
+ case SetterPassword:
+ mURLProxy->URL()->SetPassword(mValue, rv);
+ break;
+
+ case SetterHost:
+ mURLProxy->URL()->SetHost(mValue, rv);
+ break;
+
+ case SetterHostname:
+ mURLProxy->URL()->SetHostname(mValue, rv);
+ break;
+
+ case SetterPort:
+ mURLProxy->URL()->SetPort(mValue, rv);
+ break;
+
+ case SetterPathname:
+ mURLProxy->URL()->SetPathname(mValue, rv);
+ break;
+
+ case SetterSearch:
+ mURLProxy->URL()->SetSearch(mValue, rv);
+ break;
+
+ case SetterHash:
+ mURLProxy->URL()->SetHash(mValue, rv);
+ break;
+ }
+
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ mFailed = true;
+ }
+
+ return true;
+ }
+
+ bool Failed() const
+ {
+ return mFailed;
+ }
+
+private:
+ const nsString mValue;
+ SetterType mType;
+ RefPtr mURLProxy;
+ bool mFailed;
+};
+
+already_AddRefed
+FinishConstructor(JSContext* aCx, WorkerPrivate* aPrivate,
+ ConstructorRunnable* aRunnable, ErrorResult& aRv)
+{
+ aRunnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr proxy = aRunnable->GetURLProxy();
+ if (NS_WARN_IF(!proxy)) {
+ aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return nullptr;
+ }
+
+ RefPtr url = new URLWorker(aPrivate, proxy);
+ return url.forget();
+}
+
+/* static */ already_AddRefed
+URLWorker::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ URL& aBase, ErrorResult& aRv)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ URLWorker& base = static_cast(aBase);
+ RefPtr runnable =
+ new ConstructorRunnable(workerPrivate, aURL, base.GetURLProxy(), aRv);
+
+ return FinishConstructor(cx, workerPrivate, runnable, aRv);
+}
+
+/* static */ already_AddRefed
+URLWorker::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional& aBase, ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ RefPtr runnable =
+ new ConstructorRunnable(workerPrivate, aURL, aBase, aRv);
+
+ return FinishConstructor(cx, workerPrivate, runnable, aRv);
+}
+
+/* static */ already_AddRefed
+URLWorker::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const nsAString& aBase, ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ Optional base;
+ base = &aBase;
+
+ RefPtr runnable =
+ new ConstructorRunnable(workerPrivate, aURL, base, aRv);
+
+ return FinishConstructor(cx, workerPrivate, runnable, aRv);
+}
+
+/* static */ void
+URLWorker::CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ const mozilla::dom::objectURLOptions& aOptions,
+ nsAString& aResult, mozilla::ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ RefPtr blobImpl = aBlob.Impl();
+ MOZ_ASSERT(blobImpl);
+
+ aRv = blobImpl->SetMutable(false);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ RefPtr runnable =
+ new CreateURLRunnable(workerPrivate, blobImpl, aOptions, aResult);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (workerPrivate->IsSharedWorker() || workerPrivate->IsServiceWorker()) {
+ WorkerGlobalScope* scope = workerPrivate->GlobalScope();
+ MOZ_ASSERT(scope);
+
+ scope->RegisterHostObjectURI(NS_ConvertUTF16toUTF8(aResult));
+ }
+}
+
+/* static */ void
+URLWorker::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aUrl,
+ ErrorResult& aRv)
+{
+ JSContext* cx = aGlobal.Context();
+ WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(cx);
+
+ RefPtr runnable =
+ new RevokeURLRunnable(workerPrivate, aUrl);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (workerPrivate->IsSharedWorker() || workerPrivate->IsServiceWorker()) {
+ WorkerGlobalScope* scope = workerPrivate->GlobalScope();
+ MOZ_ASSERT(scope);
+
+ scope->UnregisterHostObjectURI(NS_ConvertUTF16toUTF8(aUrl));
+ }
+}
+
+URLWorker::URLWorker(WorkerPrivate* aWorkerPrivate, URLProxy* aURLProxy)
+ : URL(nullptr)
+ , mWorkerPrivate(aWorkerPrivate)
+ , mURLProxy(aURLProxy)
+{}
+
+URLWorker::~URLWorker()
+{
+ if (mURLProxy) {
+ mWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr runnable =
+ new TeardownURLRunnable(mURLProxy);
+ mURLProxy = nullptr;
+
+ if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
+ NS_ERROR("Failed to dispatch teardown runnable!");
+ }
+ }
+}
+
+void
+URLWorker::GetHref(nsAString& aHref, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHref, aHref,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetHref(const nsAString& aHref, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHref, aHref,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ if (runnable->Failed()) {
+ aRv.ThrowTypeError(aHref);
+ return;
+ }
+
+ UpdateURLSearchParams();
+}
+
+void
+URLWorker::GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterOrigin, aOrigin,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterProtocol, aProtocol,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetProtocol(const nsAString& aProtocol, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterProtocol,
+ aProtocol, mURLProxy);
+
+ runnable->Dispatch(aRv);
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetUsername(nsAString& aUsername, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterUsername, aUsername,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetUsername(const nsAString& aUsername, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterUsername,
+ aUsername, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetPassword(nsAString& aPassword, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterPassword, aPassword,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetPassword(const nsAString& aPassword, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterPassword,
+ aPassword, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetHost(nsAString& aHost, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHost, aHost,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetHost(const nsAString& aHost, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHost,
+ aHost, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetHostname(nsAString& aHostname, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHostname, aHostname,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetHostname(const nsAString& aHostname, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHostname,
+ aHostname, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetPort(nsAString& aPort, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterPort, aPort,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetPort(const nsAString& aPort, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterPort,
+ aPort, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetPathname(nsAString& aPathname, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterPathname,
+ aPathname, mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetPathname(const nsAString& aPathname, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterPathname,
+ aPathname, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::GetSearch(nsAString& aSearch, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterSearch, aSearch,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::GetHash(nsAString& aHash, ErrorResult& aRv) const
+{
+ RefPtr runnable =
+ new GetterRunnable(mWorkerPrivate, GetterRunnable::GetterHash, aHash,
+ mURLProxy);
+
+ runnable->Dispatch(aRv);
+}
+
+void
+URLWorker::SetHash(const nsAString& aHash, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterHash,
+ aHash, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv)
+{
+ RefPtr runnable =
+ new SetterRunnable(mWorkerPrivate, SetterRunnable::SetterSearch,
+ aSearch, mURLProxy);
+
+ runnable->Dispatch(aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+
+ MOZ_ASSERT(!runnable->Failed());
+}
+
+void
+URLWorker::UpdateURLSearchParams()
+{
+ if (mSearchParams) {
+ nsAutoString search;
+
+ ErrorResult rv;
+ GetSearch(search, rv);
+ if (NS_WARN_IF(rv.Failed())) {
+ rv.SuppressException();
+ }
+
+ mSearchParams->ParseInput(NS_ConvertUTF16toUTF8(Substring(search, 1)));
+ }
+}
+
+} // anonymous namespace
+
+///////////////////////////////////////////////////////////////////////////////
+// Base class for URL
+///////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(URL, mParent, mSearchParams)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(URL)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(URL)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(URL)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+URL::WrapObject(JSContext* aCx, JS::Handle aGivenProto)
+{
+ return URLBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/* static */ already_AddRefed
+URL::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ URL& aBase, ErrorResult& aRv)
+{
+ if (NS_IsMainThread()) {
+ return URLMainThread::Constructor(aGlobal, aURL, aBase, aRv);
+ }
+
+ return URLWorker::Constructor(aGlobal, aURL, aBase, aRv);
+}
+
+/* static */ already_AddRefed
+URL::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional& aBase, ErrorResult& aRv)
+{
+ if (NS_IsMainThread()) {
+ return URLMainThread::Constructor(aGlobal, aURL, aBase, aRv);
+ }
+
+ return URLWorker::Constructor(aGlobal, aURL, aBase, aRv);
+}
+
+/* static */ already_AddRefed
+URL::WorkerConstructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const nsAString& aBase, ErrorResult& aRv)
+{
+ return URLWorker::Constructor(aGlobal, aURL, aBase, aRv);
+}
+
+void
+URL::CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv)
+{
+ if (NS_IsMainThread()) {
+ URLMainThread::CreateObjectURL(aGlobal, aBlob, aOptions, aResult, aRv);
+ } else {
+ URLWorker::CreateObjectURL(aGlobal, aBlob, aOptions, aResult, aRv);
+ }
+}
+
+void
+URL::CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ URLMainThread::CreateObjectURL(aGlobal, aStream, aOptions, aResult, aRv);
+}
+
+void
+URL::CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+ const objectURLOptions& aOptions,
+ nsAString& aResult,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ URLMainThread::CreateObjectURL(aGlobal, aSource, aOptions, aResult, aRv);
+}
+
+void
+URL::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
+ ErrorResult& aRv)
+{
+ if (NS_IsMainThread()) {
+ URLMainThread::RevokeObjectURL(aGlobal, aURL, aRv);
+ } else {
+ URLWorker::RevokeObjectURL(aGlobal, aURL, aRv);
+ }
+}
+
+URLSearchParams*
+URL::SearchParams()
+{
+ CreateSearchParamsIfNeeded();
+ return mSearchParams;
+}
+
+bool IsChromeURI(nsIURI* aURI)
+{
+ bool isChrome = false;
+ if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)))
+ return isChrome;
+ return false;
+}
+
+void
+URL::CreateSearchParamsIfNeeded()
+{
+ if (!mSearchParams) {
+ mSearchParams = new URLSearchParams(mParent, this);
+ UpdateURLSearchParams();
+ }
+}
+
+void
+URL::SetSearch(const nsAString& aSearch, ErrorResult& aRv)
+{
+ SetSearchInternal(aSearch, aRv);
+ UpdateURLSearchParams();
+}
+
+void
+URL::URLSearchParamsUpdated(URLSearchParams* aSearchParams)
+{
+ MOZ_ASSERT(mSearchParams);
+ MOZ_ASSERT(mSearchParams == aSearchParams);
+
+ nsAutoString search;
+ mSearchParams->Serialize(search);
+
+ ErrorResult rv;
+ SetSearchInternal(search, rv);
+ NS_WARN_IF(rv.Failed());
+ rv.SuppressException();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/url/URL.h b/dom/url/URL.h
new file mode 100644
index 000000000000..3f38e8066dc4
--- /dev/null
+++ b/dom/url/URL.h
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_URL_h
+#define mozilla_dom_URL_h
+
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/URLSearchParams.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsString.h"
+#include "nsWrapperCache.h"
+
+class nsISupports;
+class nsIURI;
+
+namespace mozilla {
+
+class ErrorResult;
+class DOMMediaStream;
+
+namespace dom {
+
+class Blob;
+class MediaSource;
+class GlobalObject;
+struct objectURLOptions;
+
+class URL : public URLSearchParamsObserver
+ , public nsWrapperCache
+{
+public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(URL)
+
+ URL(nsISupports* aParent)
+ : mParent(aParent)
+ {}
+
+ // WebIDL methods
+ nsISupports* GetParentObject() const
+ {
+ return mParent;
+ }
+
+ virtual JSObject*
+ WrapObject(JSContext* aCx, JS::Handle aGivenProto) override;
+
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ URL& aBase, ErrorResult& aRv);
+
+ static already_AddRefed
+ Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const Optional& aBase, ErrorResult& aRv);
+
+ // Helper for Fetch API
+ static already_AddRefed
+ WorkerConstructor(const GlobalObject& aGlobal, const nsAString& aURL,
+ const nsAString& aBase, ErrorResult& aRv);
+
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, Blob& aBlob,
+ const objectURLOptions& aOptions,
+ nsAString& aResult, ErrorResult& aRv);
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, DOMMediaStream& aStream,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv);
+
+ static void
+ CreateObjectURL(const GlobalObject& aGlobal, MediaSource& aSource,
+ const objectURLOptions& aOptions, nsAString& aResult,
+ ErrorResult& aRv);
+
+ static void
+ RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL,
+ ErrorResult& aRv);
+
+ virtual void
+ GetHref(nsAString& aHref, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetHref(const nsAString& aHref, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetOrigin(nsAString& aOrigin, ErrorResult& aRv) const = 0;
+
+ virtual void
+ GetProtocol(nsAString& aProtocol, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetProtocol(const nsAString& aProtocol, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetUsername(nsAString& aUsername, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetUsername(const nsAString& aUsername, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetPassword(nsAString& aPassword, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetPassword(const nsAString& aPassword, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetHost(nsAString& aHost, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetHost(const nsAString& aHost, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetHostname(nsAString& aHostname, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetHostname(const nsAString& aHostname, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetPort(nsAString& aPort, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetPort(const nsAString& aPort, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetPathname(nsAString& aPathname, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetPathname(const nsAString& aPathname, ErrorResult& aRv) = 0;
+
+ virtual void
+ GetSearch(nsAString& aSearch, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetSearch(const nsAString& aSearch, ErrorResult& aRv);
+
+ URLSearchParams* SearchParams();
+
+ virtual void
+ GetHash(nsAString& aHost, ErrorResult& aRv) const = 0;
+
+ virtual void
+ SetHash(const nsAString& aHash, ErrorResult& aRv) = 0;
+
+ void Stringify(nsAString& aRetval, ErrorResult& aRv) const
+ {
+ GetHref(aRetval, aRv);
+ }
+
+ // URLSearchParamsObserver
+ void
+ URLSearchParamsUpdated(URLSearchParams* aSearchParams) override;
+
+protected:
+ virtual ~URL()
+ {}
+
+ virtual void
+ UpdateURLSearchParams() = 0;
+
+ virtual void
+ SetSearchInternal(const nsAString& aSearch, ErrorResult& aRv) = 0;
+
+ void CreateSearchParamsIfNeeded();
+
+ nsCOMPtr mParent;
+ RefPtr mSearchParams;
+};
+
+bool IsChromeURI(nsIURI* aURI);
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_URL_h */
diff --git a/dom/base/URLSearchParams.cpp b/dom/url/URLSearchParams.cpp
similarity index 100%
rename from dom/base/URLSearchParams.cpp
rename to dom/url/URLSearchParams.cpp
diff --git a/dom/base/URLSearchParams.h b/dom/url/URLSearchParams.h
similarity index 100%
rename from dom/base/URLSearchParams.h
rename to dom/url/URLSearchParams.h
diff --git a/dom/url/moz.build b/dom/url/moz.build
new file mode 100644
index 000000000000..915227079bb5
--- /dev/null
+++ b/dom/url/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.dom += [
+ 'URL.h',
+ 'URLSearchParams.h',
+]
+
+UNIFIED_SOURCES += [
+ 'URL.cpp',
+ 'URLSearchParams.cpp',
+]
+
+LOCAL_INCLUDES += [
+ '../workers',
+]
+
+MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += [ 'tests/chrome.ini' ]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
diff --git a/dom/url/tests/chrome.ini b/dom/url/tests/chrome.ini
new file mode 100644
index 000000000000..1e9cfc43a09c
--- /dev/null
+++ b/dom/url/tests/chrome.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+skip-if = buildapp == 'b2g' || os == 'android'
+support-files =
+ file_url.jsm
+ file_worker_url.jsm
+ test_bug883784.jsm
+ jsm_url_worker.js
+ !/dom/workers/test/dom_worker_helper.js
+
+[test_url.xul]
+[test_worker_url.xul]
+[test_bug883784.xul]
diff --git a/dom/base/test/file_url.jsm b/dom/url/tests/file_url.jsm
similarity index 100%
rename from dom/base/test/file_url.jsm
rename to dom/url/tests/file_url.jsm
diff --git a/dom/workers/test/file_url.jsm b/dom/url/tests/file_worker_url.jsm
similarity index 93%
rename from dom/workers/test/file_url.jsm
rename to dom/url/tests/file_worker_url.jsm
index c6e33acdc983..d0bbf62fed24 100644
--- a/dom/workers/test/file_url.jsm
+++ b/dom/url/tests/file_worker_url.jsm
@@ -15,11 +15,10 @@ this.checkFromJSM = function checkFromJSM(ok, is, finish) {
}
}
- var self = this;
worker.onerror = function(event) {
is(event.target, worker);
ok(false, "Worker had an error: " + event.data);
- self.worker.terminate();
+ worker.terminate();
finish();
};
diff --git a/dom/workers/test/jsm_url_worker.js b/dom/url/tests/jsm_url_worker.js
similarity index 100%
rename from dom/workers/test/jsm_url_worker.js
rename to dom/url/tests/jsm_url_worker.js
diff --git a/dom/url/tests/mochitest.ini b/dom/url/tests/mochitest.ini
new file mode 100644
index 000000000000..370ef2c2ba9d
--- /dev/null
+++ b/dom/url/tests/mochitest.ini
@@ -0,0 +1,20 @@
+[DEFAULT]
+support-files =
+ url_worker.js
+ urlApi_worker.js
+ urlSearchParams_worker.js
+ url_exceptions_worker.js
+
+[test_url.html]
+[test_url_data.html]
+[test_url_empty_port.html]
+[test_url_malformedHost.html]
+[test_urlExceptions.html]
+[test_urlSearchParams.html]
+[test_urlSearchParams_utf8.html]
+[test_urlutils_stringify.html]
+[test_worker_url.html]
+[test_worker_urlApi.html]
+[test_worker_url_exceptions.html]
+[test_worker_urlSearchParams.html]
+[test_unknown_url_origin.html]
diff --git a/dom/workers/test/test_bug883784.jsm b/dom/url/tests/test_bug883784.jsm
similarity index 100%
rename from dom/workers/test/test_bug883784.jsm
rename to dom/url/tests/test_bug883784.jsm
diff --git a/dom/workers/test/test_bug883784.xul b/dom/url/tests/test_bug883784.xul
similarity index 85%
rename from dom/workers/test/test_bug883784.xul
rename to dom/url/tests/test_bug883784.xul
index 89217bca4093..4fdcf5eb10bf 100644
--- a/dom/workers/test/test_bug883784.xul
+++ b/dom/url/tests/test_bug883784.xul
@@ -11,7 +11,7 @@
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-
+