diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js index 8c528941c8ca..42e4c19149ca 100644 --- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -431,8 +431,6 @@ pref("services.push.pingInterval", 1800000); // 30 minutes pref("services.push.requestTimeout", 10000); // enable udp wakeup support pref("services.push.udp.wakeupEnabled", true); -// port on which UDP server socket is bound -pref("services.push.udp.port", 2442); // NetworkStats #ifdef MOZ_B2G_RIL @@ -612,6 +610,10 @@ pref("dom.ipc.processPriorityManager.temporaryPriorityLockMS", 5000); // /still/ have the same niceness; we'd effectively have erased NSPR's thread // priorities. +// The kernel can only accept 6 (OomScoreAdjust, KillUnderMB) pairs. But it is +// okay, kernel will still kill processes with larger OomScoreAdjust first even +// its OomScoreAdjust don't have a corresponding KillUnderMB. + pref("hal.processPriorityManager.gonk.MASTER.OomScoreAdjust", 0); pref("hal.processPriorityManager.gonk.MASTER.KillUnderMB", 4); pref("hal.processPriorityManager.gonk.MASTER.Nice", 0); @@ -624,6 +626,9 @@ pref("hal.processPriorityManager.gonk.FOREGROUND.OomScoreAdjust", 134); pref("hal.processPriorityManager.gonk.FOREGROUND.KillUnderMB", 6); pref("hal.processPriorityManager.gonk.FOREGROUND.Nice", 1); +pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.OomScoreAdjust", 200); +pref("hal.processPriorityManager.gonk.FOREGROUND_KEYBOARD.Nice", 1); + pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.OomScoreAdjust", 400); pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.KillUnderMB", 7); pref("hal.processPriorityManager.gonk.BACKGROUND_PERCEIVABLE.Nice", 7); diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 284515d07c48..ab54d6f64509 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,4 +1,4 @@ { - "revision": "44efe8a2b5ce1abc18fb4da92fe774d08290bd31", + "revision": "df1654f408ae9ff3bdb276ad2e243bfecfd47087", "repo_path": "/integration/gaia-central" } diff --git a/browser/base/content/test/general/browser_save_link-perwindowpb.js b/browser/base/content/test/general/browser_save_link-perwindowpb.js index acce07765020..63cb4917d9da 100644 --- a/browser/base/content/test/general/browser_save_link-perwindowpb.js +++ b/browser/base/content/test/general/browser_save_link-perwindowpb.js @@ -111,6 +111,9 @@ function test() { function onExamineResponse(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") { + return; + } try { let cookies = channel.getResponseHeader("set-cookie"); // From browser/base/content/test/general/bug792715.sjs, we receive a Set-Cookie @@ -122,6 +125,9 @@ function test() { function onModifyRequest(subject) { let channel = subject.QueryInterface(Ci.nsIHttpChannel); + if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/general/bug792517.sjs") { + return; + } try { let cookies = channel.getRequestHeader("cookie"); // From browser/base/content/test/general/bug792715.sjs, we should never send a diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 2423b0cfc083..a4ad46dbc3bb 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -1204,7 +1204,7 @@ BrowserGlue.prototype = { // potential hangs (bug 518683). The asynchronous shutdown operations // will then be handled by a shutdown service (bug 435058). waitingForHTMLExportToComplete = false; - BookmarkHTMLUtils.exportToFile(FileUtils.getFile("BMarks", [])).then( + BookmarkHTMLUtils.exportToFile(Services.dirsvc.get("BMarks", Ci.nsIFile)).then( function onSuccess() { waitingForHTMLExportToComplete = true; }, diff --git a/browser/components/sessionstore/test/unit/xpcshell.ini b/browser/components/sessionstore/test/unit/xpcshell.ini index 65de4b2db187..10737448add5 100644 --- a/browser/components/sessionstore/test/unit/xpcshell.ini +++ b/browser/components/sessionstore/test/unit/xpcshell.ini @@ -7,10 +7,6 @@ support-files = data/sessionstore_valid.js [test_backup.js] [test_backup_once.js] [test_startup_nosession_sync.js] -# bug 845190 - thread pool wasn't shutdown assertions -skip-if = (os == "win" || "linux") && debug [test_startup_nosession_async.js] [test_startup_session_sync.js] -# bug 845190 - thread pool wasn't shutdown assertions -skip-if = (os == "win" || "linux") && debug [test_startup_session_async.js] diff --git a/browser/devtools/app-manager/app-projects.js b/browser/devtools/app-manager/app-projects.js index 32e482d23c09..78349f0ce768 100644 --- a/browser/devtools/app-manager/app-projects.js +++ b/browser/devtools/app-manager/app-projects.js @@ -88,12 +88,19 @@ const IDB = { const store = new ObservableObject({ projects:[] }); +let loadDeferred = promise.defer(); + IDB.open().then(function (projects) { store.object.projects = projects; AppProjects.emit("ready", store.object.projects); + loadDeferred.resolve(); }); const AppProjects = { + load: function() { + return loadDeferred.promise; + }, + addPackaged: function(folder) { let project = { type: "packaged", diff --git a/browser/devtools/app-manager/content/projects.js b/browser/devtools/app-manager/content/projects.js index 02eea4068048..ed3ed1a3a40c 100644 --- a/browser/devtools/app-manager/content/projects.js +++ b/browser/devtools/app-manager/content/projects.js @@ -39,10 +39,8 @@ let UI = { this.template = new Template(document.body, AppProjects.store, Utils.l10n); this.template.start(); - AppProjects.store.on("set", (event,path,value) => { - if (path == "projects") { - AppProjects.store.object.projects.forEach(UI.validate); - } + AppProjects.load().then(() => { + AppProjects.store.object.projects.forEach(UI.validate); }); }, @@ -320,10 +318,11 @@ let UI = { // And only when the toolbox is opened, release the button button.disabled = false; }, - (msg) => { + (err) => { button.disabled = false; - alert(msg); - this.connection.log(msg); + let message = err.error ? err.error + ": " + err.message : String(err); + alert(message); + this.connection.log(message); }); }, diff --git a/browser/devtools/commandline/BuiltinCommands.jsm b/browser/devtools/commandline/BuiltinCommands.jsm index 16b6247acdfc..ff084badd54a 100644 --- a/browser/devtools/commandline/BuiltinCommands.jsm +++ b/browser/devtools/commandline/BuiltinCommands.jsm @@ -27,6 +27,10 @@ XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils", "resource:///modules/devtools/AppCacheUtils.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); /* CmdAddon ---------------------------------------------------------------- */ @@ -1721,160 +1725,159 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppCacheUtils", } var document = args.chrome? context.environment.chromeDocument : context.environment.document; + var deferred = context.defer(); if (args.delay > 0) { - var deferred = context.defer(); document.defaultView.setTimeout(function Command_screenshotDelay() { - let reply = this.grabScreen(document, args.filename, args.clipboard, - args.fullpage); - deferred.resolve(reply); + let promise = this.grabScreen(document, args.filename, args.clipboard, + args.fullpage); + promise.then(deferred.resolve, deferred.reject); }.bind(this), args.delay * 1000); - return deferred.promise; } else { - return this.grabScreen(document, args.filename, args.clipboard, - args.fullpage, args.selector); + let promise = this.grabScreen(document, args.filename, args.clipboard, + args.fullpage, args.selector); + promise.then(deferred.resolve, deferred.reject); } + return deferred.promise; }, grabScreen: function(document, filename, clipboard, fullpage, node) { - let window = document.defaultView; - let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); - let left = 0; - let top = 0; - let width; - let height; - let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); + return Task.spawn(function() { + let window = document.defaultView; + let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); + let left = 0; + let top = 0; + let width; + let height; + let div = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); - if (!fullpage) { - if (!node) { - left = window.scrollX; - top = window.scrollY; - width = window.innerWidth; - height = window.innerHeight; - } else { - let lh = new LayoutHelpers(window); - let rect = lh.getRect(node, window); - top = rect.top; - left = rect.left; - width = rect.width; - height = rect.height; - } - } else { - width = window.innerWidth + window.scrollMaxX; - height = window.innerHeight + window.scrollMaxY; - } - canvas.width = width; - canvas.height = height; - - let ctx = canvas.getContext("2d"); - ctx.drawWindow(window, left, top, width, height, "#fff"); - let data = canvas.toDataURL("image/png", ""); - - let loadContext = document.defaultView - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsILoadContext); - - try { - if (clipboard) { - let io = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService); - let channel = io.newChannel(data, null, null); - let input = channel.open(); - let imgTools = Cc["@mozilla.org/image/tools;1"] - .getService(Ci.imgITools); - - let container = {}; - imgTools.decodeImageData(input, channel.contentType, container); - - let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] - .createInstance(Ci.nsISupportsInterfacePointer); - wrapped.data = container.value; - - let trans = Cc["@mozilla.org/widget/transferable;1"] - .createInstance(Ci.nsITransferable); - trans.init(loadContext); - trans.addDataFlavor(channel.contentType); - trans.setTransferData(channel.contentType, wrapped, -1); - - let clipid = Ci.nsIClipboard; - let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); - clip.setData(trans, null, clipid.kGlobalClipboard); - div.textContent = gcli.lookup("screenshotCopied"); - return div; - } - } - catch (ex) { - div.textContent = gcli.lookup("screenshotErrorCopying"); - return div; - } - - let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); - - // Create a name for the file if not present - if (filename == FILENAME_DEFAULT_VALUE) { - let date = new Date(); - let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + - "-" + date.getDate(); - dateString = dateString.split("-").map(function(part) { - if (part.length == 1) { - part = "0" + part; + if (!fullpage) { + if (!node) { + left = window.scrollX; + top = window.scrollY; + width = window.innerWidth; + height = window.innerHeight; + } else { + let lh = new LayoutHelpers(window); + let rect = lh.getRect(node, window); + top = rect.top; + left = rect.left; + width = rect.width; + height = rect.height; } - return part; - }).join("-"); - let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; - filename = gcli.lookupFormat("screenshotGeneratedFilename", - [dateString, timeString]) + ".png"; - } - // Check there is a .png extension to filename - else if (!filename.match(/.png$/i)) { - filename += ".png"; - } + } else { + width = window.innerWidth + window.scrollMaxX; + height = window.innerHeight + window.scrollMaxY; + } + canvas.width = width; + canvas.height = height; - // If the filename is relative, tack it onto the download directory - if (!filename.match(/[\\\/]/)) { - let downloadMgr = Cc["@mozilla.org/download-manager;1"] - .getService(Ci.nsIDownloadManager); - let tempfile = downloadMgr.userDownloadsDirectory; - tempfile.append(filename); - filename = tempfile.path; - } + let ctx = canvas.getContext("2d"); + ctx.drawWindow(window, left, top, width, height, "#fff"); + let data = canvas.toDataURL("image/png", ""); - try { - file.initWithPath(filename); - } catch (ex) { - div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; - return div; - } + let loadContext = document.defaultView + .QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsILoadContext); - let ioService = Cc["@mozilla.org/network/io-service;1"] - .getService(Ci.nsIIOService); + if (clipboard) { + try { + let io = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + let channel = io.newChannel(data, null, null); + let input = channel.open(); + let imgTools = Cc["@mozilla.org/image/tools;1"] + .getService(Ci.imgITools); - let Persist = Ci.nsIWebBrowserPersist; - let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] - .createInstance(Persist); - persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | - Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + let container = {}; + imgTools.decodeImageData(input, channel.contentType, container); - let source = ioService.newURI(data, "UTF8", null); - persist.saveURI(source, null, null, null, null, file, loadContext); + let wrapped = Cc["@mozilla.org/supports-interface-pointer;1"] + .createInstance(Ci.nsISupportsInterfacePointer); + wrapped.data = container.value; - div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename + - "\""; - div.addEventListener("click", function openFile() { - div.removeEventListener("click", openFile); - file.reveal(); + let trans = Cc["@mozilla.org/widget/transferable;1"] + .createInstance(Ci.nsITransferable); + trans.init(loadContext); + trans.addDataFlavor(channel.contentType); + trans.setTransferData(channel.contentType, wrapped, -1); + + let clipid = Ci.nsIClipboard; + let clip = Cc["@mozilla.org/widget/clipboard;1"].getService(clipid); + clip.setData(trans, null, clipid.kGlobalClipboard); + div.textContent = gcli.lookup("screenshotCopied"); + } + catch (ex) { + div.textContent = gcli.lookup("screenshotErrorCopying"); + } + throw new Task.Result(div); + } + + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); + + // Create a name for the file if not present + if (filename == FILENAME_DEFAULT_VALUE) { + let date = new Date(); + let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) + + "-" + date.getDate(); + dateString = dateString.split("-").map(function(part) { + if (part.length == 1) { + part = "0" + part; + } + return part; + }).join("-"); + let timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0]; + filename = gcli.lookupFormat("screenshotGeneratedFilename", + [dateString, timeString]) + ".png"; + } + // Check there is a .png extension to filename + else if (!filename.match(/.png$/i)) { + filename += ".png"; + } + // If the filename is relative, tack it onto the download directory + if (!filename.match(/[\\\/]/)) { + let tempfile = yield Downloads.getPreferredDownloadsDirectory(); + tempfile.append(filename); + filename = tempfile.path; + } + + try { + file.initWithPath(filename); + } catch (ex) { + div.textContent = gcli.lookup("screenshotErrorSavingToFile") + " " + filename; + throw new Task.Result(div); + } + + let ioService = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + + let Persist = Ci.nsIWebBrowserPersist; + let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] + .createInstance(Persist); + persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES | + Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION; + + let source = ioService.newURI(data, "UTF8", null); + persist.saveURI(source, null, null, null, null, file, loadContext); + + div.textContent = gcli.lookup("screenshotSavedToFile") + " \"" + filename + + "\""; + div.addEventListener("click", function openFile() { + div.removeEventListener("click", openFile); + file.reveal(); + }); + div.style.cursor = "pointer"; + let image = document.createElement("div"); + let previewHeight = parseInt(256*height/width); + image.setAttribute("style", + "width:256px; height:" + previewHeight + "px;" + + "max-height: 256px;" + + "background-image: url('" + data + "');" + + "background-size: 256px " + previewHeight + "px;" + + "margin: 4px; display: block"); + div.appendChild(image); + throw new Task.Result(div); }); - div.style.cursor = "pointer"; - let image = document.createElement("div"); - let previewHeight = parseInt(256*height/width); - image.setAttribute("style", - "width:256px; height:" + previewHeight + "px;" + - "max-height: 256px;" + - "background-image: url('" + data + "');" + - "background-size: 256px " + previewHeight + "px;" + - "margin: 4px; display: block"); - div.appendChild(image); - return div; } }); }(this)); diff --git a/browser/metro/base/content/ContextCommands.js b/browser/metro/base/content/ContextCommands.js index 7c9d7716b03a..a9f2d4df6961 100644 --- a/browser/metro/base/content/ContextCommands.js +++ b/browser/metro/base/content/ContextCommands.js @@ -349,20 +349,22 @@ var ContextCommands = { picker.appendFilters(Ci.nsIFilePicker.filterImages); // prefered save location - var dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); - picker.displayDirectory = dnldMgr.userDownloadsDirectory; - try { - let lastDir = Services.prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); - if (this.isAccessibleDirectory(lastDir)) - picker.displayDirectory = lastDir; - } - catch (e) { } + Task.spawn(function() { + picker.displayDirectory = yield Downloads.getPreferredDownloadsDirectory(); - this._picker = picker; - this._pickerUrl = mediaURL; - this._pickerContentDisp = aPopupState.contentDisposition; - this._contentType = aPopupState.contentType; - picker.open(ContextCommands); + try { + let lastDir = Services.prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); + if (this.isAccessibleDirectory(lastDir)) + picker.displayDirectory = lastDir; + } + catch (e) { } + + this._picker = picker; + this._pickerUrl = mediaURL; + this._pickerContentDisp = aPopupState.contentDisposition; + this._contentType = aPopupState.contentType; + picker.open(ContextCommands); + }.bind(this)); }, /* diff --git a/browser/metro/base/content/browser-scripts.js b/browser/metro/base/content/browser-scripts.js index bf9d239fb88f..e38884cb7d53 100644 --- a/browser/metro/base/content/browser-scripts.js +++ b/browser/metro/base/content/browser-scripts.js @@ -10,7 +10,10 @@ Cu.import("resource://gre/modules/Services.jsm"); * JS modules */ -XPCOMUtils.defineLazyModuleGetter(this , "FormHistory", +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "FormHistory", "resource://gre/modules/FormHistory.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", @@ -97,7 +100,7 @@ let ScriptContexts = {}; ["CommandUpdater", "chrome://browser/content/commandUtil.js"], ["ContextCommands", "chrome://browser/content/ContextCommands.js"], ["Bookmarks", "chrome://browser/content/bookmarks.js"], - ["Downloads", "chrome://browser/content/downloads.js"], + ["MetroDownloadsView", "chrome://browser/content/downloads.js"], ["ConsolePanelView", "chrome://browser/content/console.js"], ["Site", "chrome://browser/content/Site.js"], ["TopSites", "chrome://browser/content/TopSites.js"], diff --git a/browser/metro/base/content/browser-ui.js b/browser/metro/base/content/browser-ui.js index 5d186c67b030..358f9e0e094e 100644 --- a/browser/metro/base/content/browser-ui.js +++ b/browser/metro/base/content/browser-ui.js @@ -142,7 +142,7 @@ var BrowserUI = { messageManager.addMessageListener("Browser:MozApplicationManifest", OfflineApps); try { - Downloads.init(); + MetroDownloadsView.init(); DialogUI.init(); FormHelperUI.init(); FindHelperUI.init(); @@ -175,7 +175,7 @@ var BrowserUI = { PanelUI.uninit(); FlyoutPanelsUI.uninit(); - Downloads.uninit(); + MetroDownloadsView.uninit(); SettingsCharm.uninit(); messageManager.removeMessageListener("Content:StateChange", this); PageThumbs.uninit(); diff --git a/browser/metro/base/content/browser.xul b/browser/metro/base/content/browser.xul index 7cd7e542bd5c..888ebf3bdcf9 100644 --- a/browser/metro/base/content/browser.xul +++ b/browser/metro/base/content/browser.xul @@ -255,7 +255,7 @@ + oncommand="MetroDownloadsView.onDownloadButton()"/> diff --git a/browser/metro/base/content/downloads.js b/browser/metro/base/content/downloads.js index 3df815a78305..b9dcf4a4f6db 100644 --- a/browser/metro/base/content/downloads.js +++ b/browser/metro/base/content/downloads.js @@ -6,7 +6,7 @@ const URI_GENERIC_ICON_DOWNLOAD = "chrome://browser/skin/images/alert-downloads-30.png"; const TOAST_URI_GENERIC_ICON_DOWNLOAD = "ms-appx:///metro/chrome/chrome/skin/images/alert-downloads-30.png" -var Downloads = { +var MetroDownloadsView = { /** * _downloadCount keeps track of the number of downloads that a single * notification bar groups together. A download is grouped with other @@ -217,15 +217,15 @@ var Downloads = { label: tryAgainButtonText, accessKey: "", callback: function() { - Downloads.manager.retryDownload(aDownload.id); + MetroDownloadsView.manager.retryDownload(aDownload.id); } }, { label: cancelButtonText, accessKey: "", callback: function() { - Downloads.cancelDownload(aDownload); - Downloads._downloadProgressIndicator.reset(); + MetroDownloadsView.cancelDownload(aDownload); + MetroDownloadsView._downloadProgressIndicator.reset(); } } ]; @@ -243,7 +243,7 @@ var Downloads = { accessKey: "", callback: function() { let fileURI = aDownload.target; - let file = Downloads._getLocalFile(fileURI); + let file = MetroDownloadsView._getLocalFile(fileURI); file.reveal(); } } @@ -264,7 +264,7 @@ var Downloads = { label: runButtonText, accessKey: "", callback: function() { - Downloads.openDownload(aDownload); + MetroDownloadsView.openDownload(aDownload); } }); } @@ -288,12 +288,12 @@ var Downloads = { switch (aTopic) { case "alertclickcallback": let fileURI = aDownload.target; - let file = Downloads._getLocalFile(fileURI); + let file = MetroDownloadsView._getLocalFile(fileURI); file.reveal(); let downloadCompleteNotification = - Downloads._notificationBox.getNotificationWithValue("download-complete"); - Downloads._notificationBox.removeNotification(downloadCompleteNotification); + MetroDownloadsView._notificationBox.getNotificationWithValue("download-complete"); + MetroDownloadsView._notificationBox.removeNotification(downloadCompleteNotification); break; } } @@ -306,11 +306,11 @@ var Downloads = { observe: function (aSubject, aTopic, aData) { switch (aTopic) { case "alertclickcallback": - Downloads.openDownload(aDownload); + MetroDownloadsView.openDownload(aDownload); let downloadCompleteNotification = - Downloads._notificationBox.getNotificationWithValue("download-complete"); - Downloads._notificationBox.removeNotification(downloadCompleteNotification); + MetroDownloadsView._notificationBox.getNotificationWithValue("download-complete"); + MetroDownloadsView._notificationBox.removeNotification(downloadCompleteNotification); break; } } @@ -406,8 +406,8 @@ var Downloads = { label: cancelButtonText, accessKey: "", callback: function() { - Downloads.cancelDownloads(); - Downloads._downloadProgressIndicator.reset(); + MetroDownloadsView.cancelDownloads(); + MetroDownloadsView._downloadProgressIndicator.reset(); } } ]; @@ -446,8 +446,8 @@ var Downloads = { switch (aEvent.type) { case "AlertClose": if (aEvent.notification.value == "download-complete" && - !Downloads._notificationBox.getNotificationWithValue("download-complete")) { - Downloads._downloadProgressIndicator.reset(); + !MetroDownloadsView._notificationBox.getNotificationWithValue("download-complete")) { + MetroDownloadsView._downloadProgressIndicator.reset(); } break; } @@ -562,10 +562,10 @@ AlertDownloadProgressListener.prototype = { let contentLength = aDownload.size; if (availableSpace > 0 && contentLength > 0 && contentLength > availableSpace) { - Downloads.showAlert(aDownload.target.spec.replace("file:", "download:"), - strings.GetStringFromName("alertDownloadsNoSpace"), - strings.GetStringFromName("alertDownloadsSize")); - Downloads.cancelDownload(aDownload); + MetroDownloadsView.showAlert(aDownload.target.spec.replace("file:", "download:"), + strings.GetStringFromName("alertDownloadsNoSpace"), + strings.GetStringFromName("alertDownloadsSize")); + MetroDownloadsView.cancelDownload(aDownload); } }, diff --git a/browser/metro/base/tests/mochitest/browser_downloads.js b/browser/metro/base/tests/mochitest/browser_downloads.js index 5c323b352d44..68cab8e2a7f0 100644 --- a/browser/metro/base/tests/mochitest/browser_downloads.js +++ b/browser/metro/base/tests/mochitest/browser_downloads.js @@ -47,7 +47,7 @@ function equalNumbers(){ } function getPromisedDbResult(aStatement) { - let dbConnection = Downloads.manager.DBConnection; + let dbConnection = MetroDownloadsView.manager.DBConnection; let statement = ("string" == typeof aStatement) ? dbConnection.createAsyncStatement( aStatement @@ -129,7 +129,7 @@ function resetDownloads(){ // Services.prefs.clearUserPref("browser.download.panel.shown"); // Ensure that data is unloaded. - let dlMgr = Downloads.manager; + let dlMgr = MetroDownloadsView.manager; let dlsToRemove = []; // Clear all completed/cancelled downloads dlMgr.cleanUp(); @@ -247,11 +247,11 @@ gTests.push({ // we're going to add stuff to the downloads db. yield spawn( gen_addDownloadRows( DownloadData ) ); - todo( false, "Check that Downloads._progressNotificationInfo and Downloads._downloadCount \ + todo( false, "Check that MetroDownloadsView._progressNotificationInfo and MetroDownloadsView._downloadCount \ have the correct length (DownloadData.length) \ May also test that the correct notifications show up for various states."); - todo(false, "Iterate through download objects in Downloads._progressNotificationInfo \ + todo(false, "Iterate through download objects in MetroDownloadsView._progressNotificationInfo \ and confirm that the downloads they refer to are the same as those in \ DownloadData."); } catch(e) { @@ -299,7 +299,7 @@ gTests.push({ is(downloadRows.length, 3, "Correct number of downloads in the db before removal"); - todo(false, "Get some download from Downloads._progressNotificationInfo, \ + todo(false, "Get some download from MetroDownloadsView._progressNotificationInfo, \ confirm that its file exists, then remove it."); // remove is async(?), wait a bit diff --git a/browser/metro/components/HelperAppDialog.js b/browser/metro/components/HelperAppDialog.js index f44f980a9209..371f68162f86 100644 --- a/browser/metro/components/HelperAppDialog.js +++ b/browser/metro/components/HelperAppDialog.js @@ -18,6 +18,10 @@ XPCOMUtils.defineLazyGetter(this, "ContentUtil", function() { Cu.import("resource:///modules/ContentUtil.jsm"); return ContentUtil; }); +XPCOMUtils.defineLazyModuleGetter(this, "Downloads", + "resource://gre/modules/Downloads.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); // ----------------------------------------------------------------------- // HelperApp Launcher Dialog @@ -134,98 +138,98 @@ HelperAppLauncherDialog.prototype = { }, promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) { - let file = null; - let prefs = Services.prefs; - - if (!aForcePrompt) { - // Check to see if the user wishes to auto save to the default download - // folder without prompting. Note that preference might not be set. - let autodownload = true; - try { - autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); - } catch (e) { } - - if (autodownload) { - // Retrieve the user's default download directory - let dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); - let defaultFolder = dnldMgr.userDownloadsDirectory; + return Task.spawn(function() { + let file = null; + let prefs = Services.prefs; + if (!aForcePrompt) { + // Check to see if the user wishes to auto save to the default download + // folder without prompting. Note that preference might not be set. + let autodownload = true; try { - file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); - } - catch (e) { - } + autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); + } catch (e) { } - // Check to make sure we have a valid directory, otherwise, prompt - if (file) - return file; + if (autodownload) { + // Retrieve the user's default download directory + let defaultFolder = yield Downloads.getPreferredDownloadsDirectory(); + + try { + file = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExt); + } + catch (e) { + } + + // Check to make sure we have a valid directory, otherwise, prompt + if (file) + throw new Task.Result(file); + } } - } - // Use file picker to show dialog. - let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let windowTitle = ""; - let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); - picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave); - picker.defaultString = aDefaultFile; + // Use file picker to show dialog. + let picker = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + let windowTitle = ""; + let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow); + picker.init(parent, windowTitle, Ci.nsIFilePicker.modeSave); + picker.defaultString = aDefaultFile; - if (aSuggestedFileExt) { - // aSuggestedFileExtension includes the period, so strip it - picker.defaultExtension = aSuggestedFileExt.substring(1); - } - else { + if (aSuggestedFileExt) { + // aSuggestedFileExtension includes the period, so strip it + picker.defaultExtension = aSuggestedFileExt.substring(1); + } + else { + try { + picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension; + } + catch (e) { } + } + + var wildCardExtension = "*"; + if (aSuggestedFileExt) { + wildCardExtension += aSuggestedFileExt; + picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension); + } + + picker.appendFilters(Ci.nsIFilePicker.filterAll); + + // Default to lastDir if it is valid, otherwise use the user's default + // downloads directory. userDownloadsDirectory should always return a + // valid directory, so we can safely default to it. + picker.displayDirectory = yield Downloads.getPreferredDownloadsDirectory(); + + // The last directory preference may not exist, which will throw. try { - picker.defaultExtension = aLauncher.MIMEInfo.primaryExtension; + let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); + if (isUsableDirectory(lastDir)) + picker.displayDirectory = lastDir; } catch (e) { } - } - var wildCardExtension = "*"; - if (aSuggestedFileExt) { - wildCardExtension += aSuggestedFileExt; - picker.appendFilter(aLauncher.MIMEInfo.description, wildCardExtension); - } - - picker.appendFilters(Ci.nsIFilePicker.filterAll); - - // Default to lastDir if it is valid, otherwise use the user's default - // downloads directory. userDownloadsDirectory should always return a - // valid directory, so we can safely default to it. - var dnldMgr = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager); - picker.displayDirectory = dnldMgr.userDownloadsDirectory; - - // The last directory preference may not exist, which will throw. - try { - let lastDir = prefs.getComplexValue("browser.download.lastDir", Ci.nsILocalFile); - if (isUsableDirectory(lastDir)) - picker.displayDirectory = lastDir; - } - catch (e) { } - - if (picker.show() == Ci.nsIFilePicker.returnCancel) { - // null result means user cancelled. - return null; - } - - // Be sure to save the directory the user chose through the Save As... - // dialog as the new browser.download.dir since the old one - // didn't exist. - file = picker.file; - - if (file) { - try { - // Remove the file so that it's not there when we ensure non-existence later; - // this is safe because for the file to exist, the user would have had to - // confirm that he wanted the file overwritten. - if (file.exists()) - file.remove(false); + if (picker.show() == Ci.nsIFilePicker.returnCancel) { + // null result means user cancelled. + throw new Task.Result(null); } - catch (e) { } - var newDir = file.parent.QueryInterface(Ci.nsILocalFile); - prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir); - file = this.validateLeafName(newDir, file.leafName, null); - } - return file; + + // Be sure to save the directory the user chose through the Save As... + // dialog as the new browser.download.dir since the old one + // didn't exist. + file = picker.file; + + if (file) { + try { + // Remove the file so that it's not there when we ensure non-existence later; + // this is safe because for the file to exist, the user would have had to + // confirm that he wanted the file overwritten. + if (file.exists()) + file.remove(false); + } + catch (e) { } + var newDir = file.parent.QueryInterface(Ci.nsILocalFile); + prefs.setComplexValue("browser.download.lastDir", Ci.nsILocalFile, newDir); + file = this.validateLeafName(newDir, file.leafName, null); + } + throw new Task.Result(file); + }.bind(this)); }, validateLeafName: function hald_validateLeafName(aLocalFile, aLeafName, aFileExt) { diff --git a/browser/modules/WindowsPreviewPerTab.jsm b/browser/modules/WindowsPreviewPerTab.jsm index dc0463a1cdd6..d9d989ad1221 100644 --- a/browser/modules/WindowsPreviewPerTab.jsm +++ b/browser/modules/WindowsPreviewPerTab.jsm @@ -397,7 +397,7 @@ function TabWindow(win) { this.win = win; this.tabbrowser = win.gBrowser; - this.previews = []; + this.previews = new Map(); for (let i = 0; i < this.tabEvents.length; i++) this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false); @@ -451,7 +451,7 @@ TabWindow.prototype = { newTab: function (tab) { let controller = new PreviewController(this, tab); // It's OK to add the preview now while the favicon still loads. - this.previews.splice(tab._tPos, 0, controller.preview); + this.previews.set(tab, controller.preview); AeroPeek.addPreview(controller.preview); // updateTitleAndTooltip relies on having controller.preview which is lazily resolved. // Now that we've updated this.previews, it will resolve successfully. @@ -485,10 +485,7 @@ TabWindow.prototype = { preview.move(null); preview.controller.wrappedJSObject.destroy(); - // We don't want to splice from the array if the tabs aren't being removed - // from the tab bar as well (as is the case when the window closes). - if (!this._destroying) - this.previews.splice(tab._tPos, 1); + this.previews.delete(tab); AeroPeek.removePreview(preview); }, @@ -501,26 +498,31 @@ TabWindow.prototype = { // Because making a tab visible requires that the tab it is next to be // visible, it is far simpler to unset the 'next' tab and recreate them all // at once. - this.previews.forEach(function (preview) { + for (let [tab, preview] of this.previews) { preview.move(null); preview.visible = enable; - }); + } this.updateTabOrdering(); }, previewFromTab: function (tab) { - return this.previews[tab._tPos]; + return this.previews.get(tab); }, updateTabOrdering: function () { + let previews = this.previews; + let tabs = this.tabbrowser.tabs; + + // Previews are internally stored using a map, so we need to iterate the + // tabbrowser's array of tabs to retrieve previews in the same order. + let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))]; + // Since the internal taskbar array has not yet been updated we must force // on it the sorting order of our local array. To do so we must walk // the local array backwards, otherwise we would send move requests in the // wrong order. See bug 522610 for details. - for (let i = this.previews.length - 1; i >= 0; i--) { - let p = this.previews[i]; - let next = i == this.previews.length - 1 ? null : this.previews[i+1]; - p.move(next); + for (let i = inorder.length - 1; i >= 0; i--) { + inorder[i].move(inorder[i + 1] || null); } }, @@ -540,11 +542,6 @@ TabWindow.prototype = { this.previewFromTab(tab).active = true; break; case "TabMove": - let oldPos = evt.detail; - let newPos = tab._tPos; - let preview = this.previews[oldPos]; - this.previews.splice(oldPos, 1); - this.previews.splice(newPos, 0, preview); this.updateTabOrdering(); break; case "tabviewshown": @@ -564,8 +561,10 @@ TabWindow.prototype = { getFaviconAsImage(aIconURL, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) { let index = self.tabbrowser.browsers.indexOf(aBrowser); // Only add it if we've found the index. The tab could have closed! - if (index != -1) - self.previews[index].icon = img; + if (index != -1) { + let tab = self.tabbrowser.tabs[index]; + self.previews.get(tab).icon = img; + } }); } } diff --git a/content/html/content/src/HTMLMediaElement.cpp b/content/html/content/src/HTMLMediaElement.cpp index f73c68e9f330..24ad24eb7e88 100644 --- a/content/html/content/src/HTMLMediaElement.cpp +++ b/content/html/content/src/HTMLMediaElement.cpp @@ -2938,6 +2938,7 @@ void HTMLMediaElement::SeekCompleted() DispatchAsyncEvent(NS_LITERAL_STRING("seeked")); // We changed whether we're seeking so we need to AddRemoveSelfReference AddRemoveSelfReference(); + mTextTracks->DidSeek(); } void HTMLMediaElement::NotifySuspendedByCache(bool aIsSuspended) @@ -3866,7 +3867,7 @@ HTMLMediaElement::AddTextTrack(TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage) { - return mTextTracks->AddTextTrack(aKind, aLabel, aLanguage); + return mTextTracks->AddTextTrack(this, aKind, aLabel, aLanguage); } } // namespace dom diff --git a/content/html/content/src/HTMLTrackElement.cpp b/content/html/content/src/HTMLTrackElement.cpp index 7f2b3d5ea95d..7a9f5bd40eb1 100644 --- a/content/html/content/src/HTMLTrackElement.cpp +++ b/content/html/content/src/HTMLTrackElement.cpp @@ -126,7 +126,7 @@ HTMLTrackElement::Track() if (!mTrack) { // We're expected to always have an internal TextTrack so create // an empty object to return if we don't already have one. - mTrack = new TextTrack(OwnerDoc()->GetParentObject()); + mTrack = new TextTrack(OwnerDoc()->GetParentObject(), mMediaParent); } return mTrack; @@ -146,7 +146,8 @@ HTMLTrackElement::CreateTextTrack() kind = TextTrackKind::Subtitles; } - mTrack = new TextTrack(OwnerDoc()->GetParentObject(), kind, label, srcLang); + mTrack = new TextTrack(OwnerDoc()->GetParentObject(), mMediaParent, kind, + label, srcLang); if (mMediaParent) { mMediaParent->AddTextTrack(mTrack); diff --git a/content/media/TextTrack.cpp b/content/media/TextTrack.cpp index 4e9ef27f3f2d..2bfd4745b092 100644 --- a/content/media/TextTrack.cpp +++ b/content/media/TextTrack.cpp @@ -6,16 +6,19 @@ #include "mozilla/dom/TextTrack.h" #include "mozilla/dom/TextTrackBinding.h" +#include "mozilla/dom/TextTrackCue.h" #include "mozilla/dom/TextTrackCueList.h" #include "mozilla/dom/TextTrackRegion.h" #include "mozilla/dom/TextTrackRegionList.h" +#include "mozilla/dom/HTMLMediaElement.h" namespace mozilla { namespace dom { -NS_IMPL_CYCLE_COLLECTION_INHERITED_4(TextTrack, +NS_IMPL_CYCLE_COLLECTION_INHERITED_5(TextTrack, nsDOMEventTargetHelper, mParent, + mMediaElement, mCueList, mActiveCueList, mRegionList) @@ -25,31 +28,46 @@ NS_IMPL_RELEASE_INHERITED(TextTrack, nsDOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TextTrack) NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper) +TextTrack::TextTrack(nsISupports* aParent) + : mParent(aParent) +{ + SetDefaultSettings(); + SetIsDOMBinding(); +} + +TextTrack::TextTrack(nsISupports* aParent, HTMLMediaElement* aMediaElement) + : mParent(aParent) + , mMediaElement(aMediaElement) +{ + SetDefaultSettings(); + SetIsDOMBinding(); +} + TextTrack::TextTrack(nsISupports* aParent, + HTMLMediaElement* aMediaElement, TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage) : mParent(aParent) - , mKind(aKind) - , mLabel(aLabel) - , mLanguage(aLanguage) - , mMode(TextTrackMode::Hidden) - , mCueList(new TextTrackCueList(aParent)) - , mActiveCueList(new TextTrackCueList(aParent)) - , mRegionList(new TextTrackRegionList(aParent)) + , mMediaElement(aMediaElement) { + SetDefaultSettings(); + mKind = aKind; + mLabel = aLabel; + mLanguage = aLanguage; SetIsDOMBinding(); } -TextTrack::TextTrack(nsISupports* aParent) - : mParent(aParent) - , mKind(TextTrackKind::Subtitles) - , mMode(TextTrackMode::Disabled) - , mCueList(new TextTrackCueList(aParent)) - , mActiveCueList(new TextTrackCueList(aParent)) - , mRegionList(new TextTrackRegionList(aParent)) +void +TextTrack::SetDefaultSettings() { - SetIsDOMBinding(); + mKind = TextTrackKind::Subtitles; + mMode = TextTrackMode::Hidden; + mCueList = new TextTrackCueList(mParent); + mActiveCueList = new TextTrackCueList(mParent); + mRegionList = new TextTrackRegionList(mParent); + mCuePos = 0; + mDirty = false; } void @@ -74,12 +92,14 @@ void TextTrack::AddCue(TextTrackCue& aCue) { mCueList->AddCue(aCue); + SetDirty(); } void TextTrack::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) { mCueList->RemoveCue(aCue, aRv); + SetDirty(); } void @@ -111,5 +131,43 @@ TextTrack::RemoveRegion(const TextTrackRegion& aRegion, ErrorResult& aRv) mRegionList->RemoveTextTrackRegion(aRegion); } +TextTrackCueList* +TextTrack::GetActiveCues() +{ + if (mMode == TextTrackMode::Disabled || !mMediaElement) { + return nullptr; + } + + // If we are dirty, i.e. an event happened that may cause the sorted mCueList + // to have changed like a seek or an insert for a cue, than we need to rebuild + // the active cue list from scratch. + if (mDirty) { + mCuePos = 0; + mDirty = true; + mActiveCueList->RemoveAll(); + } + + double playbackTime = mMediaElement->CurrentTime(); + // Remove all the cues from the active cue list whose end times now occur + // earlier then the current playback time. When we reach a cue whose end time + // is valid we can safely stop iterating as the list is sorted. + for (uint32_t i = 0; i < mActiveCueList->Length() && + (*mActiveCueList)[i]->EndTime() < playbackTime; i++) { + mActiveCueList->RemoveCueAt(i); + } + // Add all the cues, starting from the position of the last cue that was + // added, that have valid start and end times for the current playback time. + // We can stop iterating safely once we encounter a cue that does not have + // valid times for the current playback time as the cue list is sorted. + for (; mCuePos < mCueList->Length(); mCuePos++) { + TextTrackCue* cue = (*mCueList)[mCuePos]; + if (cue->StartTime() > playbackTime || cue->EndTime() < playbackTime) { + break; + } + mActiveCueList->AddCue(*cue); + } + return mActiveCueList; +} + } // namespace dom } // namespace mozilla diff --git a/content/media/TextTrack.h b/content/media/TextTrack.h index 3f75571aa009..266041ef744f 100644 --- a/content/media/TextTrack.h +++ b/content/media/TextTrack.h @@ -20,6 +20,7 @@ class TextTrackCue; class TextTrackCueList; class TextTrackRegion; class TextTrackRegionList; +class HTMLMediaElement; class TextTrack MOZ_FINAL : public nsDOMEventTargetHelper { @@ -29,10 +30,15 @@ public: TextTrack(nsISupports* aParent); TextTrack(nsISupports* aParent, + HTMLMediaElement* aMediaElement); + TextTrack(nsISupports* aParent, + HTMLMediaElement* aMediaElement, TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage); + void SetDefaultSettings(); + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aScope) MOZ_OVERRIDE; @@ -76,13 +82,7 @@ public: return mCueList; } - TextTrackCueList* GetActiveCues() const - { - if (mMode == TextTrackMode::Disabled) { - return nullptr; - } - return mActiveCueList; - } + TextTrackCueList* GetActiveCues(); TextTrackRegionList* GetRegions() const { @@ -101,11 +101,13 @@ public: void AddCue(TextTrackCue& aCue); void RemoveCue(TextTrackCue& aCue, ErrorResult& aRv); void CueChanged(TextTrackCue& aCue); + void SetDirty() { mDirty = true; } IMPL_EVENT_HANDLER(cuechange) private: nsCOMPtr mParent; + nsRefPtr mMediaElement; TextTrackKind mKind; nsString mLabel; @@ -117,6 +119,9 @@ private: nsRefPtr mCueList; nsRefPtr mActiveCueList; nsRefPtr mRegionList; + + uint32_t mCuePos; + bool mDirty; }; } // namespace dom diff --git a/content/media/TextTrackCueList.cpp b/content/media/TextTrackCueList.cpp index 66aa32072f2c..aa968b5c6c3a 100644 --- a/content/media/TextTrackCueList.cpp +++ b/content/media/TextTrackCueList.cpp @@ -10,6 +10,20 @@ namespace mozilla { namespace dom { +class CompareCuesByTime +{ +public: + bool Equals(TextTrackCue* aOne, TextTrackCue* aTwo) const { + return aOne->StartTime() == aTwo->StartTime() && + aOne->EndTime() == aTwo->EndTime(); + } + bool LessThan(TextTrackCue* aOne, TextTrackCue* aTwo) const { + return aOne->StartTime() < aTwo->StartTime() || + (aOne->StartTime() == aTwo->StartTime() && + aOne->EndTime() < aTwo->EndTime()); + } +}; + NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(TextTrackCueList, mParent, mList) NS_IMPL_CYCLE_COLLECTING_ADDREF(TextTrackCueList) @@ -48,6 +62,12 @@ TextTrackCueList::IndexedGetter(uint32_t aIndex, bool& aFound) return aFound ? mList[aIndex] : nullptr; } +TextTrackCue* +TextTrackCueList::operator[](uint32_t aIndex) +{ + return mList.SafeElementAt(aIndex, nullptr); +} + TextTrackCue* TextTrackCueList::GetCueById(const nsAString& aId) { @@ -64,22 +84,36 @@ TextTrackCueList::GetCueById(const nsAString& aId) } void -TextTrackCueList::AddCue(TextTrackCue& cue) +TextTrackCueList::AddCue(TextTrackCue& aCue) { - if (mList.Contains(&cue)) { + if (mList.Contains(&aCue)) { return; } - mList.AppendElement(&cue); + mList.InsertElementSorted(&aCue, CompareCuesByTime()); } void -TextTrackCueList::RemoveCue(TextTrackCue& cue, ErrorResult& aRv) +TextTrackCueList::RemoveCue(TextTrackCue& aCue, ErrorResult& aRv) { - if (!mList.Contains(&cue)) { + if (!mList.Contains(&aCue)) { aRv.Throw(NS_ERROR_DOM_NOT_FOUND_ERR); return; } - mList.RemoveElement(&cue); + mList.RemoveElement(&aCue); +} + +void +TextTrackCueList::RemoveCueAt(uint32_t aIndex) +{ + if (aIndex < mList.Length()) { + mList.RemoveElementAt(aIndex); + } +} + +void +TextTrackCueList::RemoveAll() +{ + mList.Clear(); } } // namespace dom diff --git a/content/media/TextTrackCueList.h b/content/media/TextTrackCueList.h index 10123eeacbba..645c59606489 100644 --- a/content/media/TextTrackCueList.h +++ b/content/media/TextTrackCueList.h @@ -45,14 +45,23 @@ public: void Update(double aTime); TextTrackCue* IndexedGetter(uint32_t aIndex, bool& aFound); + TextTrackCue* operator[](uint32_t aIndex); TextTrackCue* GetCueById(const nsAString& aId); - void AddCue(TextTrackCue& cue); - void RemoveCue(TextTrackCue& cue, ErrorResult& aRv); + // Adds a cue to mList by performing an insertion sort on mList. + // We expect most files to already be sorted, so an insertion sort starting + // from the end of the current array should be more efficient than a general + // sort step after all cues are loaded. + void AddCue(TextTrackCue& aCue); + void RemoveCue(TextTrackCue& aCue, ErrorResult& aRv); + void RemoveCueAt(uint32_t aIndex); + void RemoveAll(); private: nsCOMPtr mParent; + // A sorted list of TextTrackCues sorted by earliest start time. If the start + // times are equal then it will be sorted by end time, earliest first. nsTArray< nsRefPtr > mList; }; diff --git a/content/media/TextTrackList.cpp b/content/media/TextTrackList.cpp index 380c8d0b16ce..f8dbc98cce64 100644 --- a/content/media/TextTrackList.cpp +++ b/content/media/TextTrackList.cpp @@ -47,11 +47,13 @@ TextTrackList::IndexedGetter(uint32_t aIndex, bool& aFound) } already_AddRefed -TextTrackList::AddTextTrack(TextTrackKind aKind, +TextTrackList::AddTextTrack(HTMLMediaElement* aMediaElement, + TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage) { - nsRefPtr track = new TextTrack(mGlobal, aKind, aLabel, aLanguage); + nsRefPtr track = new TextTrack(mGlobal, aMediaElement, aKind, + aLabel, aLanguage); mTextTracks.AppendElement(track); // TODO: dispatch addtrack event return track.forget(); @@ -76,5 +78,13 @@ TextTrackList::RemoveTextTrack(const TextTrack& aTrack) mTextTracks.RemoveElement(&aTrack); } +void +TextTrackList::DidSeek() +{ + for (uint32_t i = 0; i < mTextTracks.Length(); i++) { + mTextTracks[i]->SetDirty(); + } +} + } // namespace dom } // namespace mozilla diff --git a/content/media/TextTrackList.h b/content/media/TextTrackList.h index c6fbd7a9932c..be307554e4ab 100644 --- a/content/media/TextTrackList.h +++ b/content/media/TextTrackList.h @@ -40,7 +40,8 @@ public: TextTrack* IndexedGetter(uint32_t aIndex, bool& aFound); - already_AddRefed AddTextTrack(TextTrackKind aKind, + already_AddRefed AddTextTrack(HTMLMediaElement* aMediaElement, + TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage); TextTrack* GetTrackById(const nsAString& aId); @@ -50,6 +51,7 @@ public: } void RemoveTextTrack(const TextTrack& aTrack); + void DidSeek(); IMPL_EVENT_HANDLER(addtrack) IMPL_EVENT_HANDLER(removetrack) diff --git a/content/media/test/Makefile.in b/content/media/test/Makefile.in index 8a5e64b31527..d507d2df3b6c 100644 --- a/content/media/test/Makefile.in +++ b/content/media/test/Makefile.in @@ -148,6 +148,7 @@ MOCHITEST_FILES = \ test_webvtt_disabled.html \ test_bug895305.html \ test_bug895091.html \ + test_bug883173.html \ $(NULL) # Don't run in suite @@ -275,6 +276,7 @@ MOCHITEST_FILES += \ basic.vtt \ region.vtt \ long.vtt \ + bug883173.vtt \ $(NULL) # Wave sample files diff --git a/content/media/test/bug883173.vtt b/content/media/test/bug883173.vtt new file mode 100644 index 000000000000..61f086bccea2 --- /dev/null +++ b/content/media/test/bug883173.vtt @@ -0,0 +1,16 @@ +WEBVTT + +00:03.000 --> 00:04.000 +Should display fifth. + +00:01.000 --> 00:02.000 +Should display first. + +00:01.000 --> 00:03.000 +Should display second. + +00:02.000 --> 00:04.000 +Should display forth. + +00:02.000 --> 00:03.000 +Should display third. diff --git a/content/media/test/test_bug883173.html b/content/media/test/test_bug883173.html new file mode 100644 index 000000000000..1376534ba30e --- /dev/null +++ b/content/media/test/test_bug883173.html @@ -0,0 +1,58 @@ + + + + + + Test for Bug 883173 - TextTrackCue(List) Sorting + + + + + +

+
+
+
+
+
+ + \ No newline at end of file diff --git a/dom/apps/tests/Makefile.in b/dom/apps/tests/Makefile.in index 6ef33afabf53..3bfeef807d1d 100644 --- a/dom/apps/tests/Makefile.in +++ b/dom/apps/tests/Makefile.in @@ -16,6 +16,7 @@ MOCHITEST_FILES = \ test_packaged_app_update.html \ test_packaged_app_common.js \ test_uninstall_errors.html \ + test_bug_795164.html \ $(NULL) MOCHITEST_CHROME_FILES = \ diff --git a/dom/apps/tests/test_bug_795164.html b/dom/apps/tests/test_bug_795164.html new file mode 100644 index 000000000000..42de6d0b4f40 --- /dev/null +++ b/dom/apps/tests/test_bug_795164.html @@ -0,0 +1,112 @@ + + + + + + Test for Bug 795164 + + + + + +Mozilla Bug 795164 +

+ +
+
+ + diff --git a/dom/apps/tests/test_uninstall_errors.html b/dom/apps/tests/test_uninstall_errors.html index 8f1bada9af43..f499829accd9 100644 --- a/dom/apps/tests/test_uninstall_errors.html +++ b/dom/apps/tests/test_uninstall_errors.html @@ -78,7 +78,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=830258 }; yield undefined; - var request = navigator.mozApps.mgmt.uninstall(app2); + request = navigator.mozApps.mgmt.uninstall(app2); request.onsuccess = function() { ok(true, "Succeed to uninstall the app2 as expected"); continueTest(); diff --git a/dom/browser-element/BrowserElementChildPreload.js b/dom/browser-element/BrowserElementChildPreload.js index 05493556af04..7682b74e841e 100644 --- a/dom/browser-element/BrowserElementChildPreload.js +++ b/dom/browser-element/BrowserElementChildPreload.js @@ -448,8 +448,12 @@ BrowserElementChild.prototype = { _iconChangedHandler: function(e) { debug('Got iconchanged: (' + e.target.href + ')'); + let icon = { href: e.target.href }; + if (e.target.getAttribute('sizes')) { + icon.sizes = e.target.getAttribute('sizes'); + } - sendAsyncMsg('iconchange', { _payload_: e.target.href }); + sendAsyncMsg('iconchange', icon); }, _openSearchHandler: function(e) { diff --git a/dom/browser-element/mochitest/browserElement_Iconchange.js b/dom/browser-element/mochitest/browserElement_Iconchange.js index 390b2734451a..df9e76e997bd 100644 --- a/dom/browser-element/mochitest/browserElement_Iconchange.js +++ b/dom/browser-element/mochitest/browserElement_Iconchange.js @@ -12,8 +12,9 @@ function createHtml(link) { return 'data:text/html,' + link + ''; } -function createLink(name) { - return ''; +function createLink(name, sizes) { + var s = sizes ? 'sizes="' + sizes + '"' : ''; + return ''; } function runTest() { @@ -40,7 +41,7 @@ function runTest() { numIconChanges++; if (numIconChanges == 1) { - is(e.detail, 'http://example.com/myicon.png'); + is(e.detail.href, 'http://example.com/myicon.png'); // We should recieve iconchange events when the user creates new links // to a favicon, but only when we listen for them @@ -57,13 +58,13 @@ function runTest() { /* allowDelayedLoad = */ false); } else if (numIconChanges == 2) { - is(e.detail, 'http://example.com/newicon.png'); + is(e.detail.href, 'http://example.com/newicon.png'); // Full new pages should trigger iconchange events iframe1.src = createHtml(createLink('3rdicon')); } else if (numIconChanges == 3) { - is(e.detail, 'http://example.com/3rdicon.png'); + is(e.detail.href, 'http://example.com/3rdicon.png'); // the rel attribute can have various space seperated values, make // sure we only pick up correct values for 'icon' @@ -74,11 +75,11 @@ function runTest() { iframe1.src = createHtml(createLink('another') + createLink('icon')); } else if (numIconChanges == 4) { - is(e.detail, 'http://example.com/another.png'); + is(e.detail.href, 'http://example.com/another.png'); // 2 events will be triggered by previous test, wait for next } else if (numIconChanges == 5) { - is(e.detail, 'http://example.com/icon.png'); + is(e.detail.href, 'http://example.com/icon.png'); // Make sure icon check is case insensitive SpecialPowers.getBrowserFrameMessageManager(iframe1) @@ -86,7 +87,12 @@ function runTest() { /* allowDelayedLoad = */ false); } else if (numIconChanges == 6) { - is(e.detail, 'http://example.com/ucaseicon.png'); + is(e.detail.href, 'http://example.com/ucaseicon.png'); + iframe1.src = createHtml(createLink('testsize', '50x50')); + } + else if (numIconChanges == 7) { + is(e.detail.href, 'http://example.com/testsize.png'); + is(e.detail.sizes, '50x50'); SimpleTest.finish(); } else { ok(false, 'Too many iconchange events.'); diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index f4cce346f363..d7520e37f8ce 100644 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -495,14 +495,17 @@ PrivilegesForApp(mozIApplication* aApp) ContentParent::GetInitialProcessPriority(Element* aFrameElement) { // Frames with mozapptype == critical which are expecting a system message - // get FOREGROUND_HIGH priority. All other frames get FOREGROUND priority. + // get FOREGROUND_HIGH priority. if (!aFrameElement) { return PROCESS_PRIORITY_FOREGROUND; } - if (!aFrameElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozapptype, - NS_LITERAL_STRING("critical"), eCaseMatters)) { + if (aFrameElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozapptype, + NS_LITERAL_STRING("keyboard"), eCaseMatters)) { + return PROCESS_PRIORITY_FOREGROUND_KEYBOARD; + } else if (!aFrameElement->AttrValueIs(kNameSpaceID_None, nsGkAtoms::mozapptype, + NS_LITERAL_STRING("critical"), eCaseMatters)) { return PROCESS_PRIORITY_FOREGROUND; } diff --git a/dom/ipc/ProcessPriorityManager.cpp b/dom/ipc/ProcessPriorityManager.cpp index 081e45d669b8..0bd4a24cd530 100644 --- a/dom/ipc/ProcessPriorityManager.cpp +++ b/dom/ipc/ProcessPriorityManager.cpp @@ -855,7 +855,9 @@ ParticularProcessPriorityManager::ComputePriority() } if (isVisible) { - return PROCESS_PRIORITY_FOREGROUND; + return HasAppType("keyboard") ? + PROCESS_PRIORITY_FOREGROUND_KEYBOARD : + PROCESS_PRIORITY_FOREGROUND; } if ((mHoldsCPUWakeLock || mHoldsHighPriorityWakeLock) && diff --git a/dom/mobilemessage/src/gonk/MmsService.js b/dom/mobilemessage/src/gonk/MmsService.js index 24fad7c7dde7..3bd6c66e6942 100644 --- a/dom/mobilemessage/src/gonk/MmsService.js +++ b/dom/mobilemessage/src/gonk/MmsService.js @@ -248,7 +248,9 @@ XPCOMUtils.defineLazyGetter(this, "gMmsConnection", function () { * otherwise. */ acquire: function acquire(callback) { + this.refCount++; this.connectTimer.cancel(); + this.disconnectTimer.cancel(); // If the MMS network is not yet connected, buffer the // MMS request and try to setup the MMS network first. @@ -280,8 +282,6 @@ XPCOMUtils.defineLazyGetter(this, "gMmsConnection", function () { return false; } - this.refCount++; - callback(true, _HTTP_STATUS_ACQUIRE_CONNECTION_SUCCESS); return true; }, @@ -554,12 +554,6 @@ XPCOMUtils.defineLazyGetter(this, "gMmsTransactionHelper", function () { } // Setup event listeners - xhr.onerror = function () { - if (DEBUG) debug("xhr error, response headers: " + - xhr.getAllResponseHeaders()); - releaseMmsConnectionAndCallback(xhr.status, null); - }; - xhr.onreadystatechange = function () { if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) { return; diff --git a/dom/push/src/PushService.jsm b/dom/push/src/PushService.jsm index 7b705df23137..de80d8adf3d3 100644 --- a/dom/push/src/PushService.jsm +++ b/dom/push/src/PushService.jsm @@ -436,6 +436,20 @@ this.PushService = { */ _willBeWokenUpByUDP: false, + /** + * Sends a message to the Push Server through an open websocket. + * typeof(msg) shall be an object + */ + _wsSendMessage: function(msg) { + if (!this._ws) { + debug("No WebSocket initialized. Cannot send a message."); + return; + } + msg = JSON.stringify(msg); + debug("Sending message: " + msg); + this._ws.sendMsg(msg); + }, + init: function() { debug("init()"); if (!prefs.get("enabled")) @@ -454,8 +468,6 @@ this.PushService = { this._requestTimeout = prefs.get("requestTimeout"); - this._udpPort = prefs.get("udp.port"); - this._startListeningIfChannelsPresent(); Services.obs.addObserver(this, "xpcom-shutdown", false); @@ -730,7 +742,7 @@ this.PushService = { // handle the exception, as the lack of a pong will lead to the socket // being reset. try { - this._ws.sendMsg('{}'); + this._wsSendMessage({}); } catch (e) { } @@ -949,7 +961,7 @@ this.PushService = { this._shutdownWS(); } - this._ws.sendMsg(JSON.stringify(data)); + this._wsSendMessage(data); // Process the next one as soon as possible. setTimeout(this._processNextRequestInQueue.bind(this), 0); }, @@ -1280,6 +1292,10 @@ this.PushService = { // Since we've had a successful connection reset the retry fail count. this._retryFailCount = 0; + // Openning an available UDP port. + // _listenForUDPWakeup will return the opened port number + this._udpPort = this._listenForUDPWakeup(); + let data = { messageType: "hello", } @@ -1305,7 +1321,7 @@ this.PushService = { // On success, ids is an array, on error its not. data["channelIDs"] = ids.map ? ids.map(function(el) { return el.channelID; }) : []; - this._ws.sendMsg(JSON.stringify(data)); + this._wsSendMessage(data); this._currentState = STATE_WAITING_FOR_HELLO; } @@ -1400,7 +1416,6 @@ this.PushService = { debug("Server closed with promise to wake up"); this._willBeWokenUpByUDP = true; // TODO: there should be no pending requests - this._listenForUDPWakeup(); } }, @@ -1424,9 +1439,11 @@ this.PushService = { this._udpServer = Cc["@mozilla.org/network/server-socket-udp;1"] .createInstance(Ci.nsIUDPServerSocket); - this._udpServer.init(this._udpPort, false); + this._udpServer.init(-1, false); this._udpServer.asyncListen(this); debug("listenForUDPWakeup listening on " + this._udpPort); + + return this._udpServer.port; }, /** @@ -1446,6 +1463,7 @@ this.PushService = { */ onStopListening: function(aServ, aStatus) { debug("UDP Server socket was shutdown. Status: " + aStatus); + this._udpPort = undefined; this._beginWSSetup(); }, diff --git a/dom/tests/mochitest/webapps/Makefile.in b/dom/tests/mochitest/webapps/Makefile.in index 948359966c5a..1c5bcd382652 100644 --- a/dom/tests/mochitest/webapps/Makefile.in +++ b/dom/tests/mochitest/webapps/Makefile.in @@ -7,7 +7,6 @@ MOCHITEST_CHROME_FILES = \ cross_origin.html \ head.js \ test_bug_765063.xul \ - test_bug_795164.xul \ test_bug_771294.xul \ test_cross_origin.xul \ test_install_app.xul \ diff --git a/dom/tests/mochitest/webapps/test_bug_795164.xul b/dom/tests/mochitest/webapps/test_bug_795164.xul deleted file mode 100644 index c43ecf32fa99..000000000000 --- a/dom/tests/mochitest/webapps/test_bug_795164.xul +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - diff --git a/hal/Hal.cpp b/hal/Hal.cpp index a05699eea12c..b1596bb3bed6 100644 --- a/hal/Hal.cpp +++ b/hal/Hal.cpp @@ -868,6 +868,8 @@ ProcessPriorityToString(ProcessPriority aPriority) return "FOREGROUND_HIGH"; case PROCESS_PRIORITY_FOREGROUND: return "FOREGROUND"; + case PROCESS_PRIORITY_FOREGROUND_KEYBOARD: + return "FOREGROUND_KEYBOARD"; case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE: return "BACKGROUND_PERCEIVABLE"; case PROCESS_PRIORITY_BACKGROUND_HOMESCREEN: @@ -914,6 +916,13 @@ ProcessPriorityToString(ProcessPriority aPriority, if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) { return "FOREGROUND:CPU_LOW"; } + case PROCESS_PRIORITY_FOREGROUND_KEYBOARD: + if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) { + return "FOREGROUND_KEYBOARD:CPU_NORMAL"; + } + if (aCPUPriority == PROCESS_CPU_PRIORITY_LOW) { + return "FOREGROUND_KEYBOARD:CPU_LOW"; + } case PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE: if (aCPUPriority == PROCESS_CPU_PRIORITY_NORMAL) { return "BACKGROUND_PERCEIVABLE:CPU_NORMAL"; diff --git a/hal/HalTypes.h b/hal/HalTypes.h index 9b9649349f8b..925bdcb97df3 100644 --- a/hal/HalTypes.h +++ b/hal/HalTypes.h @@ -83,6 +83,7 @@ enum ProcessPriority { PROCESS_PRIORITY_BACKGROUND, PROCESS_PRIORITY_BACKGROUND_HOMESCREEN, PROCESS_PRIORITY_BACKGROUND_PERCEIVABLE, + PROCESS_PRIORITY_FOREGROUND_KEYBOARD, // Any priority greater than or equal to FOREGROUND is considered // "foreground" for the purposes of priority testing, for example // CurrentProcessIsForeground(). diff --git a/mobile/android/base/GeckoPreferences.java b/mobile/android/base/GeckoPreferences.java index ce5761efd5ca..17dd4c65e8c8 100644 --- a/mobile/android/base/GeckoPreferences.java +++ b/mobile/android/base/GeckoPreferences.java @@ -8,6 +8,7 @@ package org.mozilla.gecko; import org.mozilla.gecko.background.announcements.AnnouncementsConstants; import org.mozilla.gecko.background.common.GlobalConstants; import org.mozilla.gecko.background.healthreport.HealthReportConstants; +import org.mozilla.gecko.preferences.SearchEnginePreference; import org.mozilla.gecko.util.GeckoEventListener; import org.mozilla.gecko.GeckoPreferenceFragment; import org.mozilla.gecko.util.ThreadUtils; @@ -44,8 +45,11 @@ import android.text.TextWatcher; import android.util.Log; import android.view.MenuItem; import android.view.View; +import android.widget.AdapterView; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; @@ -87,9 +91,15 @@ public class GeckoPreferences @Override protected void onCreate(Bundle savedInstanceState) { - // For fragment-capable devices, display the default fragment if no explicit fragment to show. + + // For Android v11+ where we use Fragments (v11+ only due to bug 866352), + // check that PreferenceActivity.EXTRA_SHOW_FRAGMENT has been set + // (or set it) before super.onCreate() is called so Android can display + // the correct Fragment resource. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB && !getIntent().hasExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT)) { + // Set up the default fragment if there is no explicit fragment to show. setupTopLevelFragmentIntent(); } @@ -97,6 +107,10 @@ public class GeckoPreferences // Use setResourceToOpen to specify these extras. Bundle intentExtras = getIntent().getExtras(); + + // For versions of Android lower than Honeycomb, use xml resources instead of + // Fragments because of an Android bug in ActionBar (described in bug 866352 and + // fixed in bug 833625). if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { int res = 0; if (intentExtras != null && intentExtras.containsKey(INTENT_EXTRA_RESOURCES)) { @@ -119,6 +133,25 @@ public class GeckoPreferences registerEventListener("Sanitize:Finished"); + // Add handling for long-press click. + // This is only for Android 3.0 and below (which use the long-press-context-menu paradigm). + final ListView mListView = getListView(); + mListView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { + // Call long-click handler if it the item implements it. + final ListAdapter listAdapter = ((ListView) parent).getAdapter(); + final Object listItem = listAdapter.getItem(position); + + // Only SearchEnginePreference handles long clicks. + if (listItem instanceof SearchEnginePreference && listItem instanceof View.OnLongClickListener) { + final View.OnLongClickListener longClickListener = (View.OnLongClickListener) listItem; + return longClickListener.onLongClick(view); + } + return false; + } + }); + if (Build.VERSION.SDK_INT >= 14) getActionBar().setHomeButtonEnabled(true); diff --git a/mobile/android/base/db/LocalBrowserDB.java b/mobile/android/base/db/LocalBrowserDB.java index 21df106f43c5..af3586b094b5 100644 --- a/mobile/android/base/db/LocalBrowserDB.java +++ b/mobile/android/base/db/LocalBrowserDB.java @@ -232,12 +232,13 @@ public class LocalBrowserDB implements BrowserDB.BrowserDBIface { @Override public Cursor getTopSites(ContentResolver cr, int limit) { - // Filter out sites that are pinned + // Filter out bookmarks that don't have real parents (e.g. pinned sites or reading list items) String selection = DBUtils.concatenateWhere("", Combined.URL + " NOT IN (SELECT " + Bookmarks.URL + " FROM bookmarks WHERE " + - DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " == ? AND " + + DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < ? AND " + DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)"); - String[] selectionArgs = DBUtils.appendSelectionArgs(new String[0], new String[] { String.valueOf(Bookmarks.FIXED_PINNED_LIST_ID) }); + String[] selectionArgs = new String[] { String.valueOf(Bookmarks.FIXED_ROOT_ID) }; + return filterAllSites(cr, new String[] { Combined._ID, Combined.URL, diff --git a/mobile/android/base/preferences/SearchEnginePreference.java b/mobile/android/base/preferences/SearchEnginePreference.java index 181a196b3732..9be33c71632c 100644 --- a/mobile/android/base/preferences/SearchEnginePreference.java +++ b/mobile/android/base/preferences/SearchEnginePreference.java @@ -16,8 +16,10 @@ import android.util.Log; import android.view.View; import android.widget.TextView; import android.widget.Toast; + import org.json.JSONException; import org.json.JSONObject; + import org.mozilla.gecko.R; import org.mozilla.gecko.gfx.BitmapUtils; import org.mozilla.gecko.util.ThreadUtils; @@ -26,7 +28,7 @@ import org.mozilla.gecko.widget.FaviconView; /** * Represents an element in the list of search engines on the preferences menu. */ -public class SearchEnginePreference extends Preference { +public class SearchEnginePreference extends Preference implements View.OnLongClickListener { private static final String LOGTAG = "SearchEnginePreference"; // Indices in button array of the AlertDialog of the three buttons. @@ -104,6 +106,13 @@ public class SearchEnginePreference extends Preference { mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString()); } + @Override + public boolean onLongClick(View view) { + // Show the preference dialog on long-press. + showDialog(); + return true; + } + /** * Configure this Preference object from the Gecko search engine JSON object. * @param geckoEngineJSON The Gecko-formatted JSON object representing the search engine. diff --git a/mobile/android/base/tests/PixelTest.java.in b/mobile/android/base/tests/PixelTest.java.in index adfb0ad79047..6cf2c79b816c 100644 --- a/mobile/android/base/tests/PixelTest.java.in +++ b/mobile/android/base/tests/PixelTest.java.in @@ -2,7 +2,6 @@ package @ANDROID_PACKAGE_NAME@.tests; import @ANDROID_PACKAGE_NAME@.*; -import android.os.Build; abstract class PixelTest extends BaseTest { private static final long PAINT_CLEAR_DELAY = 10000; // milliseconds @@ -10,11 +9,7 @@ abstract class PixelTest extends BaseTest { protected final PaintedSurface loadAndGetPainted(String url) { Actions.RepeatedEventExpecter paintExpecter = mActions.expectPaint(); loadUrl(url); - // Skip this on the Tegras (Android 2.2) since they are too slow, - // which results in too many intermittent failures. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { - verifyHomePagerHidden(); - } + verifyHomePagerHidden(); paintExpecter.blockUntilClear(PAINT_CLEAR_DELAY); paintExpecter.unregisterListener(); PaintedSurface p = mDriver.getPaintedSurface(); diff --git a/toolkit/components/osfile/modules/osfile_async_worker.js b/toolkit/components/osfile/modules/osfile_async_worker.js index 990ac1fdb646..fdff79b029dc 100644 --- a/toolkit/components/osfile/modules/osfile_async_worker.js +++ b/toolkit/components/osfile/modules/osfile_async_worker.js @@ -12,384 +12,379 @@ if (this.Components) { (function(exports) { "use strict"; + importScripts("resource://gre/modules/osfile.jsm"); + + let LOG = exports.OS.Shared.LOG.bind(exports.OS.Shared.LOG, "Agent"); + + /** + * Communications with the controller. + * + * Accepts messages: + * {fun:function_name, args:array_of_arguments_or_null, id:id} + * + * Sends messages: + * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id} + */ + self.onmessage = function onmessage(msg) { + let data = msg.data; + LOG("Received message", data); + let id = data.id; + + let start; + let options; + if (data.args) { + options = data.args[data.args.length - 1]; + } + // If |outExecutionDuration| option was supplied, start measuring the + // duration of the operation. + if (options && typeof options === "object" && "outExecutionDuration" in options) { + start = Date.now(); + } + + let result; + let exn; + let durationMs; try { - importScripts("resource://gre/modules/osfile.jsm"); + let method = data.fun; + LOG("Calling method", method); + result = Agent[method].apply(Agent, data.args); + LOG("Method", method, "succeeded"); + } catch (ex) { + exn = ex; + LOG("Error while calling agent method", exn, exn.stack); + } - let LOG = exports.OS.Shared.LOG.bind(exports.OS.Shared.LOG, "Agent"); + if (start) { + // Record duration + durationMs = Date.now() - start; + LOG("Method took", durationMs, "ms"); + } - /** - * Communications with the controller. - * - * Accepts messages: - * {fun:function_name, args:array_of_arguments_or_null, id:id} - * - * Sends messages: - * {ok: result, id:id} / {fail: serialized_form_of_OS.File.Error, id:id} - */ - self.onmessage = function onmessage(msg) { - let data = msg.data; - LOG("Received message", data); - let id = data.id; + // Now, post a reply, possibly as an uncaught error. + // We post this message from outside the |try ... catch| block + // to avoid capturing errors that take place during |postMessage| and + // built-in serialization. + if (!exn) { + LOG("Sending positive reply", result, "id is", id); + if (result instanceof Transfer) { + // Take advantage of zero-copy transfers + self.postMessage({ok: result.data, id: id, durationMs: durationMs}, + result.transfers); + } else { + self.postMessage({ok: result, id:id, durationMs: durationMs}); + } + } else if (exn == StopIteration) { + // StopIteration cannot be serialized automatically + LOG("Sending back StopIteration"); + self.postMessage({StopIteration: true, id: id, durationMs: durationMs}); + } else if (exn instanceof exports.OS.File.Error) { + LOG("Sending back OS.File error", exn, "id is", id); + // Instances of OS.File.Error know how to serialize themselves + // (deserialization ensures that we end up with OS-specific + // instances of |OS.File.Error|) + self.postMessage({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs}); + } else { + LOG("Sending back regular error", exn, exn.stack, "id is", id); + // Other exceptions do not, and should be propagated through DOM's + // built-in mechanism for uncaught errors, although this mechanism + // may lose interesting information. + throw exn; + } + }; - let start; - let options; - if (data.args) { - options = data.args[data.args.length - 1]; - } - // If |outExecutionDuration| option was supplied, start measuring the - // duration of the operation. - if (options && typeof options === "object" && "outExecutionDuration" in options) { - start = Date.now(); - } + /** + * A data structure used to track opened resources + */ + let ResourceTracker = function ResourceTracker() { + // A number used to generate ids + this._idgen = 0; + // A map from id to resource + this._map = new Map(); + }; + ResourceTracker.prototype = { + /** + * Get a resource from its unique identifier. + */ + get: function(id) { + let result = this._map.get(id); + if (result == null) { + return result; + } + return result.resource; + }, + /** + * Remove a resource from its unique identifier. + */ + remove: function(id) { + if (!this._map.has(id)) { + throw new Error("Cannot find resource id " + id); + } + this._map.delete(id); + }, + /** + * Add a resource, return a new unique identifier + * + * @param {*} resource A resource. + * @param {*=} info Optional information. For debugging purposes. + * + * @return {*} A unique identifier. For the moment, this is a number, + * but this might not remain the case forever. + */ + add: function(resource, info) { + let id = this._idgen++; + this._map.set(id, {resource:resource, info:info}); + return id; + }, + /** + * Return a list of all open resources i.e. the ones still present in + * ResourceTracker's _map. + */ + listOpenedResources: function listOpenedResources() { + return [resource.info.path for ([id, resource] of this._map)]; + } + }; - let result; - let exn; - let durationMs; - try { - let method = data.fun; - LOG("Calling method", method); - result = Agent[method].apply(Agent, data.args); - LOG("Method", method, "succeeded"); - } catch (ex) { - exn = ex; - LOG("Error while calling agent method", exn, exn.stack); - } + /** + * A map of unique identifiers to opened files. + */ + let OpenedFiles = new ResourceTracker(); - if (start) { - // Record duration - durationMs = Date.now() - start; - LOG("Method took", durationMs, "ms"); - } + /** + * Execute a function in the context of a given file. + * + * @param {*} id A unique identifier, as used by |OpenFiles|. + * @param {Function} f A function to call. + * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception. + * @return The return value of |f()| + * + * This function attempts to get the file matching |id|. If + * the file exists, it executes |f| within the |this| set + * to the corresponding file. Otherwise, it throws an error. + */ + let withFile = function withFile(id, f, ignoreAbsent) { + let file = OpenedFiles.get(id); + if (file == null) { + if (!ignoreAbsent) { + throw OS.File.Error.closed("accessing file"); + } + return undefined; + } + return f.call(file); + }; - // Now, post a reply, possibly as an uncaught error. - // We post this message from outside the |try ... catch| block - // to avoid capturing errors that take place during |postMessage| and - // built-in serialization. - if (!exn) { - LOG("Sending positive reply", result, "id is", id); - if (result instanceof Transfer) { - // Take advantage of zero-copy transfers - self.postMessage({ok: result.data, id: id, durationMs: durationMs}, - result.transfers); - } else { - self.postMessage({ok: result, id:id, durationMs: durationMs}); + let OpenedDirectoryIterators = new ResourceTracker(); + let withDir = function withDir(fd, f, ignoreAbsent) { + let file = OpenedDirectoryIterators.get(fd); + if (file == null) { + if (!ignoreAbsent) { + throw OS.File.Error.closed("accessing directory"); + } + return undefined; + } + if (!(file instanceof File.DirectoryIterator)) { + throw new Error("file is not a directory iterator " + file.__proto__.toSource()); + } + return f.call(file); + }; + + let Type = exports.OS.Shared.Type; + + let File = exports.OS.File; + + /** + * A constructor used to transfer data to the caller + * without copy. + * + * @param {*} data The data to return to the caller. + * @param {Array} transfers An array of Transferable + * values that should be moved instead of being copied. + * + * @constructor + */ + let Transfer = function Transfer(data, transfers) { + this.data = data; + this.transfers = transfers; + }; + + /** + * The agent. + * + * It is in charge of performing method-specific deserialization + * of messages, calling the function/method of OS.File and serializing + * back the results. + */ + let Agent = { + // Update worker's OS.Shared.DEBUG flag message from controller. + SET_DEBUG: function SET_DEBUG (aDEBUG) { + exports.OS.Shared.DEBUG = aDEBUG; + }, + // Return worker's current OS.Shared.DEBUG value to controller. + // Note: This is used for testing purposes. + GET_DEBUG: function GET_DEBUG () { + return exports.OS.Shared.DEBUG; + }, + // Report file descriptors leaks. + System_shutdown: function System_shutdown () { + // Return information about both opened files and opened + // directory iterators. + return { + openedFiles: OpenedFiles.listOpenedResources(), + openedDirectoryIterators: + OpenedDirectoryIterators.listOpenedResources() + }; + }, + // Functions of OS.File + stat: function stat(path) { + return exports.OS.File.Info.toMsg( + exports.OS.File.stat(Type.path.fromMsg(path))); + }, + getCurrentDirectory: function getCurrentDirectory() { + return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory()); + }, + setCurrentDirectory: function setCurrentDirectory(path) { + File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path)); + }, + copy: function copy(sourcePath, destPath, options) { + return File.copy(Type.path.fromMsg(sourcePath), + Type.path.fromMsg(destPath), options); + }, + move: function move(sourcePath, destPath, options) { + return File.move(Type.path.fromMsg(sourcePath), + Type.path.fromMsg(destPath), options); + }, + makeDir: function makeDir(path, options) { + return File.makeDir(Type.path.fromMsg(path), options); + }, + removeEmptyDir: function removeEmptyDir(path, options) { + return File.removeEmptyDir(Type.path.fromMsg(path), options); + }, + remove: function remove(path) { + return File.remove(Type.path.fromMsg(path)); + }, + open: function open(path, mode, options) { + let filePath = Type.path.fromMsg(path); + let file = File.open(filePath, mode, options); + return OpenedFiles.add(file, { + // Adding path information to keep track of opened files + // to report leaks when debugging. + path: filePath + }); + }, + read: function read(path, bytes, options) { + let data = File.read(Type.path.fromMsg(path), bytes); + return new Transfer({buffer: data.buffer, byteOffset: data.byteOffset, byteLength: data.byteLength}, [data.buffer]); + }, + exists: function exists(path) { + return File.exists(Type.path.fromMsg(path)); + }, + writeAtomic: function writeAtomic(path, buffer, options) { + if (options.tmpPath) { + options.tmpPath = Type.path.fromMsg(options.tmpPath); + } + return File.writeAtomic(Type.path.fromMsg(path), + Type.voidptr_t.fromMsg(buffer), + options + ); + }, + new_DirectoryIterator: function new_DirectoryIterator(path, options) { + let directoryPath = Type.path.fromMsg(path); + let iterator = new File.DirectoryIterator(directoryPath, options); + return OpenedDirectoryIterators.add(iterator, { + // Adding path information to keep track of opened directory + // iterators to report leaks when debugging. + path: directoryPath + }); + }, + // Methods of OS.File + File_prototype_close: function close(fd) { + return withFile(fd, + function do_close() { + try { + return this.close(); + } finally { + OpenedFiles.remove(fd); } - } else if (exn == StopIteration) { - // StopIteration cannot be serialized automatically - LOG("Sending back StopIteration"); - self.postMessage({StopIteration: true, id: id, durationMs: durationMs}); - } else if (exn instanceof exports.OS.File.Error) { - LOG("Sending back OS.File error", exn, "id is", id); - // Instances of OS.File.Error know how to serialize themselves - // (deserialization ensures that we end up with OS-specific - // instances of |OS.File.Error|) - self.postMessage({fail: exports.OS.File.Error.toMsg(exn), id:id, durationMs: durationMs}); - } else { - LOG("Sending back regular error", exn, exn.stack, "id is", id); - // Other exceptions do not, and should be propagated through DOM's - // built-in mechanism for uncaught errors, although this mechanism - // may lose interesting information. - throw exn; - } - }; - - /** - * A data structure used to track opened resources - */ - let ResourceTracker = function ResourceTracker() { - // A number used to generate ids - this._idgen = 0; - // A map from id to resource - this._map = new Map(); - }; - ResourceTracker.prototype = { - /** - * Get a resource from its unique identifier. - */ - get: function(id) { - let result = this._map.get(id); - if (result == null) { - return result; - } - return result.resource; - }, - /** - * Remove a resource from its unique identifier. - */ - remove: function(id) { - if (!this._map.has(id)) { - throw new Error("Cannot find resource id " + id); - } - this._map.delete(id); - }, - /** - * Add a resource, return a new unique identifier - * - * @param {*} resource A resource. - * @param {*=} info Optional information. For debugging purposes. - * - * @return {*} A unique identifier. For the moment, this is a number, - * but this might not remain the case forever. - */ - add: function(resource, info) { - let id = this._idgen++; - this._map.set(id, {resource:resource, info:info}); - return id; - }, - /** - * Return a list of all open resources i.e. the ones still present in - * ResourceTracker's _map. - */ - listOpenedResources: function listOpenedResources() { - return [resource.info.path for ([id, resource] of this._map)]; - } - }; - - /** - * A map of unique identifiers to opened files. - */ - let OpenedFiles = new ResourceTracker(); - - /** - * Execute a function in the context of a given file. - * - * @param {*} id A unique identifier, as used by |OpenFiles|. - * @param {Function} f A function to call. - * @param {boolean} ignoreAbsent If |true|, the error is ignored. Otherwise, the error causes an exception. - * @return The return value of |f()| - * - * This function attempts to get the file matching |id|. If - * the file exists, it executes |f| within the |this| set - * to the corresponding file. Otherwise, it throws an error. - */ - let withFile = function withFile(id, f, ignoreAbsent) { - let file = OpenedFiles.get(id); - if (file == null) { - if (!ignoreAbsent) { - throw OS.File.Error.closed("accessing file"); - } - return undefined; - } - return f.call(file); - }; - - let OpenedDirectoryIterators = new ResourceTracker(); - let withDir = function withDir(fd, f, ignoreAbsent) { - let file = OpenedDirectoryIterators.get(fd); - if (file == null) { - if (!ignoreAbsent) { - throw OS.File.Error.closed("accessing directory"); - } - return undefined; - } - if (!(file instanceof File.DirectoryIterator)) { - throw new Error("file is not a directory iterator " + file.__proto__.toSource()); - } - return f.call(file); - }; - - let Type = exports.OS.Shared.Type; - - let File = exports.OS.File; - - /** - * A constructor used to transfer data to the caller - * without copy. - * - * @param {*} data The data to return to the caller. - * @param {Array} transfers An array of Transferable - * values that should be moved instead of being copied. - * - * @constructor - */ - let Transfer = function Transfer(data, transfers) { - this.data = data; - this.transfers = transfers; - }; - - /** - * The agent. - * - * It is in charge of performing method-specific deserialization - * of messages, calling the function/method of OS.File and serializing - * back the results. - */ - let Agent = { - // Update worker's OS.Shared.DEBUG flag message from controller. - SET_DEBUG: function SET_DEBUG (aDEBUG) { - exports.OS.Shared.DEBUG = aDEBUG; - }, - // Return worker's current OS.Shared.DEBUG value to controller. - // Note: This is used for testing purposes. - GET_DEBUG: function GET_DEBUG () { - return exports.OS.Shared.DEBUG; - }, - // Report file descriptors leaks. - System_shutdown: function System_shutdown () { - // Return information about both opened files and opened - // directory iterators. - return { - openedFiles: OpenedFiles.listOpenedResources(), - openedDirectoryIterators: - OpenedDirectoryIterators.listOpenedResources() - }; - }, - // Functions of OS.File - stat: function stat(path) { - return exports.OS.File.Info.toMsg( - exports.OS.File.stat(Type.path.fromMsg(path))); - }, - getCurrentDirectory: function getCurrentDirectory() { - return exports.OS.Shared.Type.path.toMsg(File.getCurrentDirectory()); - }, - setCurrentDirectory: function setCurrentDirectory(path) { - File.setCurrentDirectory(exports.OS.Shared.Type.path.fromMsg(path)); - }, - copy: function copy(sourcePath, destPath, options) { - return File.copy(Type.path.fromMsg(sourcePath), - Type.path.fromMsg(destPath), options); - }, - move: function move(sourcePath, destPath, options) { - return File.move(Type.path.fromMsg(sourcePath), - Type.path.fromMsg(destPath), options); - }, - makeDir: function makeDir(path, options) { - return File.makeDir(Type.path.fromMsg(path), options); - }, - removeEmptyDir: function removeEmptyDir(path, options) { - return File.removeEmptyDir(Type.path.fromMsg(path), options); - }, - remove: function remove(path) { - return File.remove(Type.path.fromMsg(path)); - }, - open: function open(path, mode, options) { - let filePath = Type.path.fromMsg(path); - let file = File.open(filePath, mode, options); - return OpenedFiles.add(file, { - // Adding path information to keep track of opened files - // to report leaks when debugging. - path: filePath - }); - }, - read: function read(path, bytes, options) { - let data = File.read(Type.path.fromMsg(path), bytes); + }); + }, + File_prototype_stat: function stat(fd) { + return withFile(fd, + function do_stat() { + return exports.OS.File.Info.toMsg(this.stat()); + }); + }, + File_prototype_read: function read(fd, nbytes, options) { + return withFile(fd, + function do_read() { + let data = this.read(nbytes, options); return new Transfer({buffer: data.buffer, byteOffset: data.byteOffset, byteLength: data.byteLength}, [data.buffer]); - }, - exists: function exists(path) { - return File.exists(Type.path.fromMsg(path)); - }, - writeAtomic: function writeAtomic(path, buffer, options) { - if (options.tmpPath) { - options.tmpPath = Type.path.fromMsg(options.tmpPath); - } - return File.writeAtomic(Type.path.fromMsg(path), - Type.voidptr_t.fromMsg(buffer), - options - ); - }, - new_DirectoryIterator: function new_DirectoryIterator(path, options) { - let directoryPath = Type.path.fromMsg(path); - let iterator = new File.DirectoryIterator(directoryPath, options); - return OpenedDirectoryIterators.add(iterator, { - // Adding path information to keep track of opened directory - // iterators to report leaks when debugging. - path: directoryPath - }); - }, - // Methods of OS.File - File_prototype_close: function close(fd) { - return withFile(fd, - function do_close() { - try { - return this.close(); - } finally { - OpenedFiles.remove(fd); - } - }); - }, - File_prototype_stat: function stat(fd) { - return withFile(fd, - function do_stat() { - return exports.OS.File.Info.toMsg(this.stat()); - }); - }, - File_prototype_read: function read(fd, nbytes, options) { - return withFile(fd, - function do_read() { - let data = this.read(nbytes, options); - return new Transfer({buffer: data.buffer, byteOffset: data.byteOffset, byteLength: data.byteLength}, [data.buffer]); - } - ); - }, - File_prototype_readTo: function readTo(fd, buffer, options) { - return withFile(fd, - function do_readTo() { - return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), - options); - }); - }, - File_prototype_write: function write(fd, buffer, options) { - return withFile(fd, - function do_write() { - return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), - options); - }); - }, - File_prototype_setPosition: function setPosition(fd, pos, whence) { - return withFile(fd, - function do_setPosition() { - return this.setPosition(pos, whence); - }); - }, - File_prototype_getPosition: function getPosition(fd) { - return withFile(fd, - function do_getPosition() { - return this.getPosition(); - }); - }, - // Methods of OS.File.DirectoryIterator - DirectoryIterator_prototype_next: function next(dir) { - return withDir(dir, - function do_next() { - try { - return File.DirectoryIterator.Entry.toMsg(this.next()); - } catch (x) { - if (x == StopIteration) { - OpenedDirectoryIterators.remove(dir); - } - throw x; - } - }, false); - }, - DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) { - return withDir(dir, - function do_nextBatch() { - let result; - try { - result = this.nextBatch(size); - } catch (x) { - OpenedDirectoryIterators.remove(dir); - throw x; - } - return result.map(File.DirectoryIterator.Entry.toMsg); - }, false); - }, - DirectoryIterator_prototype_close: function close(dir) { - return withDir(dir, - function do_close() { - this.close(); - OpenedDirectoryIterators.remove(dir); - }, true);// ignore error to support double-closing |DirectoryIterator| - }, - DirectoryIterator_prototype_exists: function exists(dir) { - return withDir(dir, - function do_exists() { - return this.exists(); - }); } - }; - } catch(ex) { - dump("WORKER ERROR DURING SETUP " + ex + "\n"); - dump("WORKER ERROR DETAIL " + ("moduleStack" in ex?ex.moduleStack:ex.stack) + "\n"); - } + ); + }, + File_prototype_readTo: function readTo(fd, buffer, options) { + return withFile(fd, + function do_readTo() { + return this.readTo(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), + options); + }); + }, + File_prototype_write: function write(fd, buffer, options) { + return withFile(fd, + function do_write() { + return this.write(exports.OS.Shared.Type.voidptr_t.fromMsg(buffer), + options); + }); + }, + File_prototype_setPosition: function setPosition(fd, pos, whence) { + return withFile(fd, + function do_setPosition() { + return this.setPosition(pos, whence); + }); + }, + File_prototype_getPosition: function getPosition(fd) { + return withFile(fd, + function do_getPosition() { + return this.getPosition(); + }); + }, + // Methods of OS.File.DirectoryIterator + DirectoryIterator_prototype_next: function next(dir) { + return withDir(dir, + function do_next() { + try { + return File.DirectoryIterator.Entry.toMsg(this.next()); + } catch (x) { + if (x == StopIteration) { + OpenedDirectoryIterators.remove(dir); + } + throw x; + } + }, false); + }, + DirectoryIterator_prototype_nextBatch: function nextBatch(dir, size) { + return withDir(dir, + function do_nextBatch() { + let result; + try { + result = this.nextBatch(size); + } catch (x) { + OpenedDirectoryIterators.remove(dir); + throw x; + } + return result.map(File.DirectoryIterator.Entry.toMsg); + }, false); + }, + DirectoryIterator_prototype_close: function close(dir) { + return withDir(dir, + function do_close() { + this.close(); + OpenedDirectoryIterators.remove(dir); + }, true);// ignore error to support double-closing |DirectoryIterator| + }, + DirectoryIterator_prototype_exists: function exists(dir) { + return withDir(dir, + function do_exists() { + return this.exists(); + }); + } + }; })(this); diff --git a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini index 92add75c8550..eb2a9a4c4ec8 100644 --- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini +++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini @@ -7,6 +7,4 @@ tail = [test_osfile_async.js] [test_profiledir.js] [test_logging.js] -# bug 845190 - thread pool wasn't shutdown assertions -skip-if = (os == "win" || "linux") && debug [test_creationDate.js] diff --git a/toolkit/components/search/tests/xpcshell/xpcshell.ini b/toolkit/components/search/tests/xpcshell/xpcshell.ini index f7df9fe22d04..324081ad3924 100644 --- a/toolkit/components/search/tests/xpcshell/xpcshell.ini +++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini @@ -15,8 +15,6 @@ support-files = [test_nocache.js] [test_645970.js] -# Bug 845190: Too many intermittent assertions on Linux (ASSERTION: thread pool wasn't shutdown) -skip-if = debug && os == "linux" [test_identifiers.js] [test_init_async_multiple.js] [test_init_async_multiple_then_sync.js] diff --git a/toolkit/mozapps/downloads/nsHelperAppDlg.js b/toolkit/mozapps/downloads/nsHelperAppDlg.js index 534de784e947..2ba3e329fcc7 100644 --- a/toolkit/mozapps/downloads/nsHelperAppDlg.js +++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js @@ -102,6 +102,8 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/DownloadLastDir.jsm", downloadModule); Components.utils.import("resource://gre/modules/DownloadPaths.jsm"); Components.utils.import("resource://gre/modules/DownloadUtils.jsm"); +Components.utils.import("resource://gre/modules/Downloads.jsm"); +Components.utils.import("resource://gre/modules/Task.jsm"); /* ctor */ @@ -203,115 +205,113 @@ nsUnknownContentTypeDialog.prototype = { getService(Components.interfaces.nsIStringBundleService). createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties"); - if (!aForcePrompt) { - // Check to see if the user wishes to auto save to the default download - // folder without prompting. Note that preference might not be set. - let autodownload = false; - try { - autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); - } catch (e) { } - - if (autodownload) { - // Retrieve the user's default download directory - let dnldMgr = Components.classes["@mozilla.org/download-manager;1"] - .getService(Components.interfaces.nsIDownloadManager); - let defaultFolder = dnldMgr.userDownloadsDirectory; - + Task.spawn(function() { + if (!aForcePrompt) { + // Check to see if the user wishes to auto save to the default download + // folder without prompting. Note that preference might not be set. + let autodownload = false; try { - result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension); - } - catch (ex) { - if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) { - let prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. - getService(Components.interfaces.nsIPromptService); + autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR); + } catch (e) { } - // Display error alert (using text supplied by back-end) - prompter.alert(this.dialog, - bundle.GetStringFromName("badPermissions.title"), - bundle.GetStringFromName("badPermissions")); + if (autodownload) { + // Retrieve the user's default download directory + let defaultFolder = yield Downloads.getPreferredDownloadsDirectory(); - aLauncher.saveDestinationAvailable(null); + try { + result = this.validateLeafName(defaultFolder, aDefaultFile, aSuggestedFileExtension); + } + catch (ex) { + if (ex.result == Components.results.NS_ERROR_FILE_ACCESS_DENIED) { + let prompter = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]. + getService(Components.interfaces.nsIPromptService); + + // Display error alert (using text supplied by back-end) + prompter.alert(this.dialog, + bundle.GetStringFromName("badPermissions.title"), + bundle.GetStringFromName("badPermissions")); + + aLauncher.saveDestinationAvailable(null); + return; + } + } + + // Check to make sure we have a valid directory, otherwise, prompt + if (result) { + aLauncher.saveDestinationAvailable(result); return; } } + } - // Check to make sure we have a valid directory, otherwise, prompt - if (result) { - aLauncher.saveDestinationAvailable(result); + // Use file picker to show dialog. + var nsIFilePicker = Components.interfaces.nsIFilePicker; + var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); + var windowTitle = bundle.GetStringFromName("saveDialogTitle"); + var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow); + picker.init(parent, windowTitle, nsIFilePicker.modeSave); + picker.defaultString = aDefaultFile; + + let gDownloadLastDir = new downloadModule.DownloadLastDir(parent); + + if (aSuggestedFileExtension) { + // aSuggestedFileExtension includes the period, so strip it + picker.defaultExtension = aSuggestedFileExtension.substring(1); + } + else { + try { + picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension; + } + catch (ex) { } + } + + var wildCardExtension = "*"; + if (aSuggestedFileExtension) { + wildCardExtension += aSuggestedFileExtension; + picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension); + } + + picker.appendFilters( nsIFilePicker.filterAll ); + + // Default to lastDir if it is valid, otherwise use the user's default + // downloads directory. userDownloadsDirectory should always return a + // valid directory, so we can safely default to it. + picker.displayDirectory = yield Downloads.getPreferredDownloadsDirectory(); + + gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) { + if (lastDir && isUsableDirectory(lastDir)) + picker.displayDirectory = lastDir; + + if (picker.show() == nsIFilePicker.returnCancel) { + // null result means user cancelled. + aLauncher.saveDestinationAvailable(null); return; } - } - } - // Use file picker to show dialog. - var nsIFilePicker = Components.interfaces.nsIFilePicker; - var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker); - var windowTitle = bundle.GetStringFromName("saveDialogTitle"); - var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow); - picker.init(parent, windowTitle, nsIFilePicker.modeSave); - picker.defaultString = aDefaultFile; + // Be sure to save the directory the user chose through the Save As... + // dialog as the new browser.download.dir since the old one + // didn't exist. + result = picker.file; - let gDownloadLastDir = new downloadModule.DownloadLastDir(parent); + if (result) { + try { + // Remove the file so that it's not there when we ensure non-existence later; + // this is safe because for the file to exist, the user would have had to + // confirm that he wanted the file overwritten. + if (result.exists()) + result.remove(false); + } + catch (e) { } + var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile); - if (aSuggestedFileExtension) { - // aSuggestedFileExtension includes the period, so strip it - picker.defaultExtension = aSuggestedFileExtension.substring(1); - } - else { - try { - picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension; - } - catch (ex) { } - } + // Do not store the last save directory as a pref inside the private browsing mode + gDownloadLastDir.setFile(aLauncher.source, newDir); - var wildCardExtension = "*"; - if (aSuggestedFileExtension) { - wildCardExtension += aSuggestedFileExtension; - picker.appendFilter(this.mLauncher.MIMEInfo.description, wildCardExtension); - } - - picker.appendFilters( nsIFilePicker.filterAll ); - - // Default to lastDir if it is valid, otherwise use the user's default - // downloads directory. userDownloadsDirectory should always return a - // valid directory, so we can safely default to it. - var dnldMgr = Components.classes["@mozilla.org/download-manager;1"] - .getService(Components.interfaces.nsIDownloadManager); - picker.displayDirectory = dnldMgr.userDownloadsDirectory; - - gDownloadLastDir.getFileAsync(aLauncher.source, function LastDirCallback(lastDir) { - if (lastDir && isUsableDirectory(lastDir)) - picker.displayDirectory = lastDir; - - if (picker.show() == nsIFilePicker.returnCancel) { - // null result means user cancelled. - aLauncher.saveDestinationAvailable(null); - return; - } - - // Be sure to save the directory the user chose through the Save As... - // dialog as the new browser.download.dir since the old one - // didn't exist. - result = picker.file; - - if (result) { - try { - // Remove the file so that it's not there when we ensure non-existence later; - // this is safe because for the file to exist, the user would have had to - // confirm that he wanted the file overwritten. - if (result.exists()) - result.remove(false); + result = this.validateLeafName(newDir, result.leafName, null); } - catch (e) { } - var newDir = result.parent.QueryInterface(Components.interfaces.nsILocalFile); - - // Do not store the last save directory as a pref inside the private browsing mode - gDownloadLastDir.setFile(aLauncher.source, newDir); - - result = this.validateLeafName(newDir, result.leafName, null); - } - aLauncher.saveDestinationAvailable(result); - }.bind(this)); + aLauncher.saveDestinationAvailable(result); + }.bind(this)); + }.bind(this)).then(null, Components.utils.reportError); }, /**