--- a/accessible/base/EventTree.cpp +++ b/accessible/base/EventTree.cpp @@ -148,16 +148,17 @@ TreeMutation::Done() for (uint32_t idx = 0; idx < mStartIdx && idx < length; idx++) { MOZ_ASSERT(mParent->mChildren[idx]->mIndexInParent == static_cast<int32_t>(idx), "Wrong index detected"); } #endif for (uint32_t idx = mStartIdx; idx < length; idx++) { mParent->mChildren[idx]->mIndexInParent = idx; + mParent->mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1; mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty; } if (mStartIdx < mParent->mChildren.Length() - 1) { mParent->mEmbeddedObjCollector = nullptr; } mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
--- a/accessible/tests/mochitest/hypertext/test_update.html +++ b/accessible/tests/mochitest/hypertext/test_update.html @@ -119,28 +119,54 @@ } this.getID = function removeChild_getID() { return "check text after removing child from '" + aContainerID + "'"; } } + function removeFirstChild(aContainer) + { + this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]); + this.eventSeq = [ + new invokerChecker(EVENT_REORDER, aContainer) + ]; + this.invoke = function removeFirstChild_invoke() + { + is(this.ht.linkCount, 2, "Wrong embedded objects count before removal"); + + getNode(aContainer).removeChild(getNode(aContainer).firstElementChild); + } + + this.finalCheck = function removeFirstChild_finalCheck() + { + // check list index before link count + is(this.ht.getLinkIndex(this.ht.firstChild), 0, "Wrong child index"); + is(this.ht.linkCount, 1, "Wrong embedded objects count after removal"); + } + + this.getID = function removeFirstChild_getID() + { + return "Remove first child and check embedded object indeces"; + } + } //gA11yEventDumpToConsole = true; // debug stuff var gQueue = null; function doTest() { gQueue = new eventQueue(); gQueue.push(new addLinks("p1")); gQueue.push(new updateText("p2")); gQueue.push(new removeChild("div1","div2", "hello my good friend", "hello friend")); + gQueue.push(new removeFirstChild("c4")); gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> @@ -164,10 +190,14 @@ <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <p id="p1"></p> <p id="p2"><b>hello</b><a>friend</a></p> <div id="div1">hello<span id="div2"> my<span id="div3"> good</span></span> friend</span></div> + <form id="c4"> + <label for="c4_input">label</label> + <input id="c4_input"> + </form> </body> </html>
--- a/b2g/app/b2g.js +++ b/b2g/app/b2g.js @@ -1050,19 +1050,16 @@ pref("dom.mozSettings.SettingsManager.ve pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false); pref("dom.mozSettings.SettingsService.verbose.enabled", false); // Controlling whether we want to allow forcing some Settings // IndexedDB transactions to be opened as readonly or keep everything as // readwrite. pref("dom.mozSettings.allowForceReadOnly", false); -// RequestSync API is enabled by default on B2G. -pref("dom.requestSync.enabled", true); - // Comma separated list of activity names that can only be provided by // the system app in dev mode. pref("dom.activities.developer_mode_only", "import-app"); // mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly // disable serviceworkers and push here to get them disabled in mulet. pref("dom.serviceWorkers.enabled", false); pref("dom.push.enabled", false);
--- a/b2g/chrome/content/shell.js +++ b/b2g/chrome/content/shell.js @@ -11,17 +11,16 @@ Cu.import('resource://gre/modules/AlarmS Cu.import('resource://gre/modules/ActivitiesService.jsm'); Cu.import('resource://gre/modules/NotificationDB.jsm'); Cu.import('resource://gre/modules/Payment.jsm'); Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import('resource://gre/modules/UserAgentOverrides.jsm'); Cu.import('resource://gre/modules/Keyboard.jsm'); Cu.import('resource://gre/modules/ErrorPage.jsm'); Cu.import('resource://gre/modules/AlertsHelper.jsm'); -Cu.import('resource://gre/modules/RequestSyncService.jsm'); Cu.import('resource://gre/modules/SystemUpdateService.jsm'); if (isGonk) { Cu.import('resource://gre/modules/NetworkStatsService.jsm'); Cu.import('resource://gre/modules/ResourceStatsService.jsm'); } Cu.import('resource://gre/modules/KillSwitchMain.jsm');
--- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -346,19 +346,16 @@ @RESPATH@/components/xpcom_xpti.xpt @RESPATH@/components/xpconnect.xpt @RESPATH@/components/xulapp.xpt @RESPATH@/components/xul.xpt @RESPATH@/components/xultmpl.xpt @RESPATH@/components/zipwriter.xpt ; JavaScript components -@RESPATH@/components/RequestSync.manifest -@RESPATH@/components/RequestSyncManager.js -@RESPATH@/components/RequestSyncScheduler.js @RESPATH@/components/ChromeNotifications.js @RESPATH@/components/ChromeNotifications.manifest @RESPATH@/components/ConsoleAPI.manifest @RESPATH@/components/ConsoleAPIStorage.js @RESPATH@/components/BrowserElementParent.manifest @RESPATH@/components/BrowserElementParent.js @RESPATH@/components/BrowserElementProxy.manifest @RESPATH@/components/BrowserElementProxy.js
--- a/browser/base/content/aboutDialog.xul +++ b/browser/base/content/aboutDialog.xul @@ -62,17 +62,17 @@ <button id="checkForUpdatesButton" align="start" label="&update.checkForUpdatesButton.label;" accesskey="&update.checkForUpdatesButton.accesskey;" oncommand="gAppUpdater.checkForUpdates();"/> <spacer flex="1"/> </hbox> <hbox id="downloadAndInstall" align="center"> <button id="downloadAndInstallButton" align="start" - oncommand="gAppUpdater.doUpdate();"/> + oncommand="gAppUpdater.startDownload();"/> <!-- label and accesskey will be filled by JS --> <spacer flex="1"/> </hbox> <hbox id="apply" align="center"> <button id="updateButton" align="start" label="&update.updateButton.label2;" accesskey="&update.updateButton.accesskey;" oncommand="gAppUpdater.buttonRestartAfterDownload();"/>
--- a/browser/base/content/browser-fullScreen.js +++ b/browser/base/content/browser-fullScreen.js @@ -126,32 +126,17 @@ var FullScreen = { // to get its corresponding browser here. let browser; if (event.target == gBrowser) { browser = event.originalTarget; } else { let topWin = event.target.ownerDocument.defaultView.top; browser = gBrowser.getBrowserForContentWindow(topWin); } - if (!browser || !this.enterDomFullscreen(browser)) { - if (document.fullscreenElement) { - // MozDOMFullscreen:Entered is dispatched synchronously in - // fullscreen change, hence we have to avoid calling this - // method synchronously here. - setTimeout(() => document.exitFullscreen(), 0); - } - break; - } - // If it is a remote browser, send a message to ask the content - // to enter fullscreen state. We don't need to do so if it is an - // in-process browser, since all related document should have - // entered fullscreen state at this point. - if (this._isRemoteBrowser(browser)) { - browser.messageManager.sendAsyncMessage("DOMFullscreen:Entered"); - } + this.enterDomFullscreen(browser); break; } case "MozDOMFullscreen:Exited": this.cleanupDomFullscreen(); break; } }, @@ -173,32 +158,40 @@ var FullScreen = { case "DOMFullscreen:Painted": { Services.obs.notifyObservers(window, "fullscreen-painted", ""); break; } } }, enterDomFullscreen : function(aBrowser) { - if (!document.fullscreenElement) - return false; + if (!document.fullscreenElement) { + return; + } // If we've received a fullscreen notification, we have to ensure that the // element that's requesting fullscreen belongs to the browser that's currently // active. If not, we exit fullscreen since the "full-screen document" isn't // actually visible now. - if (gBrowser.selectedBrowser != aBrowser) { - return false; + if (!aBrowser || gBrowser.selectedBrowser != aBrowser || + // The top-level window has lost focus since the request to enter + // full-screen was made. Cancel full-screen. + Services.focus.activeWindow != window) { + // This function is called synchronously in fullscreen change, so + // we have to avoid calling exitFullscreen synchronously here. + setTimeout(() => document.exitFullscreen(), 0); + return; } - let focusManager = Services.focus; - if (focusManager.activeWindow != window) { - // The top-level window has lost focus since the request to enter - // full-screen was made. Cancel full-screen. - return false; + // If it is a remote browser, send a message to ask the content + // to enter fullscreen state. We don't need to do so if it is an + // in-process browser, since all related document should have + // entered fullscreen state at this point. + if (this._isRemoteBrowser(aBrowser)) { + aBrowser.messageManager.sendAsyncMessage("DOMFullscreen:Entered"); } document.documentElement.setAttribute("inDOMFullscreen", true); if (gFindBarInitialized) { gFindBar.close(true); } @@ -206,40 +199,38 @@ var FullScreen = { gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen); gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen); gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen); // Add listener to detect when the fullscreen window is re-focused. // If a fullscreen window loses focus, we show a warning when the // fullscreen window is refocused. window.addEventListener("activate", this); - - return true; }, cleanup: function () { if (!window.fullScreen) { MousePosTracker.removeListener(this); document.removeEventListener("keypress", this._keyToggleCallback, false); document.removeEventListener("popupshown", this._setPopupOpen, false); document.removeEventListener("popuphidden", this._setPopupOpen, false); } }, cleanupDomFullscreen: function () { + window.messageManager + .broadcastAsyncMessage("DOMFullscreen:CleanUp"); + this._WarningBox.close(); gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen); gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen); gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen); window.removeEventListener("activate", this); document.documentElement.removeAttribute("inDOMFullscreen"); - - window.messageManager - .broadcastAsyncMessage("DOMFullscreen:CleanUp"); }, _isRemoteBrowser: function (aBrowser) { return gMultiProcessBrowser && aBrowser.getAttribute("remote") == "true"; }, get _windowUtils() { return window.QueryInterface(Ci.nsIInterfaceRequestor)
--- a/browser/base/content/content.js +++ b/browser/base/content/content.js @@ -1014,16 +1014,17 @@ var PageInfoListener = { let docInfo = {}; docInfo.title = document.title; docInfo.location = document.location.toString(); docInfo.referrer = document.referrer; docInfo.compatMode = document.compatMode; docInfo.contentType = document.contentType; docInfo.characterSet = document.characterSet; docInfo.lastModified = document.lastModified; + docInfo.principal = document.nodePrincipal; let documentURIObject = {}; documentURIObject.spec = document.documentURIObject.spec; documentURIObject.originCharset = document.documentURIObject.originCharset; docInfo.documentURIObject = documentURIObject; docInfo.isContentWindowPrivate = PrivateBrowsingUtils.isContentWindowPrivate(content);
--- a/browser/base/content/pageinfo/pageInfo.js +++ b/browser/base/content/pageinfo/pageInfo.js @@ -370,29 +370,30 @@ function loadPageInfo(frameOuterWindowID // Get initial pageInfoData needed to display the general, feeds, permission and security tabs. mm.addMessageListener("PageInfo:data", function onmessage(message) { mm.removeMessageListener("PageInfo:data", onmessage); pageInfoData = message.data; let docInfo = pageInfoData.docInfo; let windowInfo = pageInfoData.windowInfo; let uri = makeURI(docInfo.documentURIObject.spec, docInfo.documentURIObject.originCharset); + let principal = docInfo.principal; gDocInfo = docInfo; gImageElement = pageInfoData.imageInfo; var titleFormat = windowInfo.isTopWindow ? "pageInfo.page.title" : "pageInfo.frame.title"; document.title = gBundle.getFormattedString(titleFormat, [docInfo.location]); document.getElementById("main-window").setAttribute("relatedUrl", docInfo.location); makeGeneralTab(pageInfoData.metaViewRows, docInfo); initFeedTab(pageInfoData.feeds); - onLoadPermission(uri); + onLoadPermission(uri, principal); securityOnLoad(uri, windowInfo); }); // Get the media elements from content script to setup the media tab. mm.addMessageListener("PageInfo:mediaData", function onmessage(message) { // Page info window was closed. if (window.closed) { mm.removeMessageListener("PageInfo:mediaData", onmessage);
--- a/browser/base/content/pageinfo/permissions.js +++ b/browser/base/content/pageinfo/permissions.js @@ -3,16 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ Components.utils.import("resource:///modules/SitePermissions.jsm"); Components.utils.import("resource://gre/modules/BrowserUtils.jsm"); const nsIQuotaManagerService = Components.interfaces.nsIQuotaManagerService; var gPermURI; +var gPermPrincipal; var gUsageRequest; var gPermissions = SitePermissions.listPermissions(); gPermissions.push("plugins"); var permissionObserver = { observe: function (aSubject, aTopic, aData) { @@ -23,21 +24,22 @@ var permissionObserver = { initRow(permission.type); else if (permission.type.startsWith("plugin")) setPluginsRadioState(); } } } }; -function onLoadPermission(uri) +function onLoadPermission(uri, principal) { var permTab = document.getElementById("permTab"); if (SitePermissions.isSupportedURI(uri)) { gPermURI = uri; + gPermPrincipal = principal; var hostText = document.getElementById("hostText"); hostText.value = gPermURI.prePath; for (var i of gPermissions) initRow(i); var os = Components.classes["@mozilla.org/observer-service;1"] .getService(Components.interfaces.nsIObserverService); os.addObserver(permissionObserver, "perm-changed", false); @@ -184,40 +186,33 @@ function initIndexedDBRow() let row = document.getElementById("perm-indexedDB-row"); let extras = document.getElementById("perm-indexedDB-extras"); row.appendChild(extras); var quotaManagerService = Components.classes["@mozilla.org/dom/quota-manager-service;1"] .getService(nsIQuotaManagerService); - let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"] - .getService(Components.interfaces.nsIScriptSecurityManager) - .createCodebasePrincipal(gPermURI, {}); gUsageRequest = - quotaManagerService.getUsageForPrincipal(principal, + quotaManagerService.getUsageForPrincipal(gPermPrincipal, onIndexedDBUsageCallback); var status = document.getElementById("indexedDBStatus"); var button = document.getElementById("indexedDBClear"); status.value = ""; status.setAttribute("hidden", "true"); button.setAttribute("hidden", "true"); } function onIndexedDBClear() { - let principal = Components.classes["@mozilla.org/scriptsecuritymanager;1"] - .getService(Components.interfaces.nsIScriptSecurityManager) - .createCodebasePrincipal(gPermURI, {}); - Components.classes["@mozilla.org/dom/quota-manager-service;1"] .getService(nsIQuotaManagerService) - .clearStoragesForPrincipal(principal); + .clearStoragesForPrincipal(gPermPrincipal); Components.classes["@mozilla.org/serviceworkers/manager;1"] .getService(Components.interfaces.nsIServiceWorkerManager) .removeAndPropagate(gPermURI.host); SitePermissions.remove(gPermURI, "indexedDB"); initIndexedDBRow(); }
--- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -507,20 +507,16 @@ @RESPATH@/components/Webapps.manifest @RESPATH@/components/AppsService.js @RESPATH@/components/AppsService.manifest @RESPATH@/components/recording-cmdline.js @RESPATH@/components/recording-cmdline.manifest @RESPATH@/components/htmlMenuBuilder.js @RESPATH@/components/htmlMenuBuilder.manifest -@RESPATH@/components/RequestSync.manifest -@RESPATH@/components/RequestSyncManager.js -@RESPATH@/components/RequestSyncScheduler.js - @RESPATH@/components/PermissionSettings.js @RESPATH@/components/PermissionSettings.manifest @RESPATH@/components/ContactManager.js @RESPATH@/components/ContactManager.manifest @RESPATH@/components/PhoneNumberService.js @RESPATH@/components/PhoneNumberService.manifest @RESPATH@/components/NotificationStorage.js @RESPATH@/components/NotificationStorage.manifest
--- a/build/moz.configure/memory.configure +++ b/build/moz.configure/memory.configure @@ -81,17 +81,18 @@ set_define(jemalloc_os_define_android, ' option('--enable-replace-malloc', help='Enable ability to dynamically replace the malloc implementation') @depends('--enable-replace-malloc', jemalloc, milestone, build_project) def replace_malloc(value, jemalloc, milestone, build_project): # Enable on central for the debugging opportunities it adds. + if value and not jemalloc: + die('--enable-replace-malloc requires --enable-jemalloc') + if value.origin != 'default': + return bool(value) or None if milestone.is_nightly and jemalloc and build_project != 'js': return True - if value and not jemalloc: - die('--enable-replace-malloc requires --enable-jemalloc') - return bool(value) or None set_config('MOZ_REPLACE_MALLOC', replace_malloc) set_define('MOZ_REPLACE_MALLOC', replace_malloc) add_old_configure_assignment('MOZ_REPLACE_MALLOC', replace_malloc)
--- a/config/system-headers +++ b/config/system-headers @@ -251,16 +251,17 @@ cairo-ps.h cairo-tee.h cairo-quartz.h cairo-win32.h cairo-xlib.h cairo-xlib-xrender.h cairo-directfb.h cairo-qpainter.h cairo-qt.h +complex dfiff.h exception ffi.h fusion/reactor.h fusion/property.h fusion/conf.h fusion/build.h fusion/hash.h
--- a/dom/apps/PermissionsTable.jsm +++ b/dom/apps/PermissionsTable.jsm @@ -467,21 +467,16 @@ this.PermissionsTable = { geolocation: privileged: DENY_ACTION, certified: ALLOW_ACTION }, "presentation-device-manage": { app: DENY_ACTION, privileged: DENY_ACTION, certified: ALLOW_ACTION }, - "requestsync-manager": { - app: DENY_ACTION, - privileged: DENY_ACTION, - certified: ALLOW_ACTION - }, "secureelement-manage": { app: DENY_ACTION, privileged: DENY_ACTION, certified: ALLOW_ACTION }, "inputport": { app: DENY_ACTION, privileged: DENY_ACTION,
--- a/dom/base/Console.cpp +++ b/dom/base/Console.cpp @@ -1044,17 +1044,16 @@ METHOD(Exception, "exception") METHOD(Debug, "debug") METHOD(Table, "table") METHOD(Clear, "clear") void Console::Trace(JSContext* aCx) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); const Sequence<JS::Value> data; Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data); } // Displays an interactive listing of all the properties of an object. METHOD(Dir, "dir"); METHOD(Dirxml, "dirxml"); @@ -1062,83 +1061,78 @@ METHOD(Dirxml, "dirxml"); METHOD(Group, "group") METHOD(GroupCollapsed, "groupCollapsed") METHOD(GroupEnd, "groupEnd") void Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); Sequence<JS::Value> data; SequenceRooter<JS::Value> rooter(aCx, &data); if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) { return; } Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data); } void Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); Sequence<JS::Value> data; SequenceRooter<JS::Value> rooter(aCx, &data); if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) { return; } Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data); } void Console::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); Sequence<JS::Value> data; SequenceRooter<JS::Value> rooter(aCx, &data); if (aData.isString() && !data.AppendElement(aData, fallible)) { return; } Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data); } void Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); - ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData); } void Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); - ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData); } void Console::ProfileMethod(JSContext* aCx, const nsAString& aAction, const Sequence<JS::Value>& aData) { - MOZ_ASSERT(mStatus == eInitialized); + if (IsShuttingDown()) { + return; + } if (!NS_IsMainThread()) { // Here we are in a worker thread. RefPtr<ConsoleProfileRunnable> runnable = new ConsoleProfileRunnable(this, aAction, aData); JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx)); runnable->Dispatch(aCx); @@ -1186,30 +1180,28 @@ Console::ProfileMethod(JSContext* aCx, c } } void Console::Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); if (!aCondition) { Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData); } } METHOD(Count, "count") void Console::NoopMethod() { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); // Nothing to do. } static nsresult StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame, ConsoleStackEntry& aStackEntry) @@ -1273,17 +1265,19 @@ ReifyStack(JSContext* aCx, nsIStackFrame // Queue a call to a console method. See the CALL_DELAY constant. void Console::Method(JSContext* aCx, MethodName aMethodName, const nsAString& aMethodString, const Sequence<JS::Value>& aData) { AssertIsOnOwningThread(); - MOZ_ASSERT(mStatus == eInitialized); + if (IsShuttingDown()) { + return; + } RefPtr<ConsoleCallData> callData(new ConsoleCallData()); ClearException ce(aCx); if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString, aData, this))) { return; @@ -2406,10 +2400,17 @@ Console::SetConsoleEventHandler(AnyCallb void Console::AssertIsOnOwningThread() const { MOZ_ASSERT(mOwningThread); MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread); } +bool +Console::IsShuttingDown() const +{ + MOZ_ASSERT(mStatus != eUnknown); + return mStatus == eShuttingDown; +} + } // namespace dom } // namespace mozilla
--- a/dom/base/Console.h +++ b/dom/base/Console.h @@ -31,18 +31,16 @@ class ConsoleRunnable; class ConsoleCallDataRunnable; class ConsoleProfileRunnable; struct ConsoleStackEntry; class Console final : public nsIObserver , public nsWrapperCache , public nsSupportsWeakReference { - ~Console(); - public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console, nsIObserver) NS_DECL_NSIOBSERVER static already_AddRefed<Console> Create(nsPIDOMWindowInner* aWindow, ErrorResult& aRv); @@ -128,16 +126,17 @@ public: RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents, ErrorResult& aRv); void SetConsoleEventHandler(AnyCallback* aHandler); private: explicit Console(nsPIDOMWindowInner* aWindow); + ~Console(); void Initialize(ErrorResult& aRv); void Shutdown(); enum MethodName @@ -338,16 +337,19 @@ private: ShouldIncludeStackTrace(MethodName aMethodName) const; JSObject* GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal); void AssertIsOnOwningThread() const; + bool + IsShuttingDown() const; + // All these nsCOMPtr are touched on main thread only. nsCOMPtr<nsPIDOMWindowInner> mWindow; nsCOMPtr<nsIConsoleAPIStorage> mStorage; RefPtr<JSObjectHolder> mSandbox; // Touched on the owner thread. nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry; nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
--- a/dom/base/EventSource.cpp +++ b/dom/base/EventSource.cpp @@ -180,17 +180,17 @@ EventSource::Close() mReadyState = CLOSED; } nsresult EventSource::Init(nsISupports* aOwner, const nsAString& aURL, bool aWithCredentials) { - if (mReadyState != CONNECTING || !PrefEnabled()) { + if (mReadyState != CONNECTING) { return NS_ERROR_DOM_SECURITY_ERR; } nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner); NS_ENSURE_STATE(sgo); // XXXbz why are we checking this? This doesn't match anything in the spec. nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext(); NS_ENSURE_STATE(scriptContext); @@ -567,23 +567,16 @@ EventSource::GetInterface(const nsIID & } return wwatch->GetPrompt(window, aIID, aResult); } return QueryInterface(aIID, aResult); } -// static -bool -EventSource::PrefEnabled(JSContext* aCx, JSObject* aGlobal) -{ - return Preferences::GetBool("dom.server-events.enabled", false); -} - nsresult EventSource::GetBaseURI(nsIURI **aBaseURI) { NS_ENSURE_ARG_POINTER(aBaseURI); *aBaseURI = nullptr; nsCOMPtr<nsIURI> baseURI;
--- a/dom/base/EventSource.h +++ b/dom/base/EventSource.h @@ -88,19 +88,16 @@ public: return mReadyState; } IMPL_EVENT_HANDLER(open) IMPL_EVENT_HANDLER(message) IMPL_EVENT_HANDLER(error) void Close(); - // Determine if preferences allow EventSource - static bool PrefEnabled(JSContext* aCx = nullptr, JSObject* aGlobal = nullptr); - virtual void DisconnectFromOwner() override; protected: virtual ~EventSource(); nsresult Init(nsISupports* aOwner, const nsAString& aURL, bool aWithCredentials);
--- a/dom/base/File.cpp +++ b/dom/base/File.cpp @@ -861,53 +861,43 @@ BlobImplFile::GetSize(ErrorResult& aRv) mLength = fileSize; } return mLength; } namespace { -class GetTypeRunnable final : public Runnable +class GetTypeRunnable final : public WorkerMainThreadRunnable { public: GetTypeRunnable(WorkerPrivate* aWorkerPrivate, - nsIEventTarget* aSyncLoopTarget, BlobImpl* aBlobImpl) - : mWorkerPrivate(aWorkerPrivate) - , mSyncLoopTarget(aSyncLoopTarget) + : WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("BlobImplFile :: GetType")) , mBlobImpl(aBlobImpl) { - MOZ_ASSERT(aWorkerPrivate); - MOZ_ASSERT(aSyncLoopTarget); MOZ_ASSERT(aBlobImpl); aWorkerPrivate->AssertIsOnWorkerThread(); } - NS_IMETHOD - Run() override + bool + MainThreadRun() override { MOZ_ASSERT(NS_IsMainThread()); nsAutoString type; mBlobImpl->GetType(type); - - RefPtr<MainThreadStopSyncLoopRunnable> runnable = - new MainThreadStopSyncLoopRunnable(mWorkerPrivate, - mSyncLoopTarget.forget(), true); - NS_WARN_IF(!runnable->Dispatch()); - return NS_OK; + return true; } private: ~GetTypeRunnable() {} - WorkerPrivate* mWorkerPrivate; - nsCOMPtr<nsIEventTarget> mSyncLoopTarget; RefPtr<BlobImpl> mBlobImpl; }; } // anonymous namespace void BlobImplFile::GetType(nsAString& aType) { @@ -920,24 +910,22 @@ BlobImplFile::GetType(nsAString& aType) if (!NS_IsMainThread()) { WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); if (!workerPrivate) { // I have no idea in which thread this method is called. We cannot // return any valid value. return; } - AutoSyncLoopHolder syncLoop(workerPrivate); + RefPtr<GetTypeRunnable> runnable = + new GetTypeRunnable(workerPrivate, this); - RefPtr<GetTypeRunnable> runnable = - new GetTypeRunnable(workerPrivate, syncLoop.EventTarget(), this); - nsresult rv = NS_DispatchToMainThread(runnable); - NS_WARN_IF(NS_FAILED(rv)); - - NS_WARN_IF(!syncLoop.Run()); + ErrorResult rv; + runnable->Dispatch(rv); + NS_WARN_IF(rv.Failed()); return; } nsresult rv; nsCOMPtr<nsIMIMEService> mimeService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return;
--- a/dom/base/WebSocket.cpp +++ b/dom/base/WebSocket.cpp @@ -282,17 +282,18 @@ namespace { class PrintErrorOnConsoleRunnable final : public WorkerMainThreadRunnable { public: PrintErrorOnConsoleRunnable(WebSocketImpl* aImpl, const char* aBundleURI, const char16_t* aError, const char16_t** aFormatStrings, uint32_t aFormatStringsLen) - : WorkerMainThreadRunnable(aImpl->mWorkerPrivate) + : WorkerMainThreadRunnable(aImpl->mWorkerPrivate, + NS_LITERAL_CSTRING("WebSocket :: print error on console")) , mImpl(aImpl) , mBundleURI(aBundleURI) , mError(aError) , mFormatStrings(aFormatStrings) , mFormatStringsLen(aFormatStringsLen) { } bool MainThreadRun() override @@ -567,17 +568,18 @@ WebSocketImpl::FailConnection(uint16_t a } namespace { class DisconnectInternalRunnable final : public WorkerMainThreadRunnable { public: explicit DisconnectInternalRunnable(WebSocketImpl* aImpl) - : WorkerMainThreadRunnable(aImpl->mWorkerPrivate) + : WorkerMainThreadRunnable(aImpl->mWorkerPrivate, + NS_LITERAL_CSTRING("WebSocket :: disconnect")) , mImpl(aImpl) { } bool MainThreadRun() override { mImpl->DisconnectInternal(); return true; } @@ -985,18 +987,19 @@ public: private: JSContext* mCx; }; class WebSocketMainThreadRunnable : public WorkerMainThreadRunnable { public: - WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate) - : WorkerMainThreadRunnable(aWorkerPrivate) + WebSocketMainThreadRunnable(WorkerPrivate* aWorkerPrivate, + const nsACString& aTelemetryKey) + : WorkerMainThreadRunnable(aWorkerPrivate, aTelemetryKey) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); } bool MainThreadRun() override { AssertIsOnMainThread(); @@ -1024,17 +1027,18 @@ protected: class InitRunnable final : public WebSocketMainThreadRunnable { public: InitRunnable(WebSocketImpl* aImpl, const nsAString& aURL, nsTArray<nsString>& aProtocolArray, const nsACString& aScriptFile, uint32_t aScriptLine, uint32_t aScriptColumn, ErrorResult& aRv, bool* aConnectionFailed) - : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate) + : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate, + NS_LITERAL_CSTRING("WebSocket :: init")) , mImpl(aImpl) , mURL(aURL) , mProtocolArray(aProtocolArray) , mScriptFile(aScriptFile) , mScriptLine(aScriptLine) , mScriptColumn(aScriptColumn) , mRv(aRv) , mConnectionFailed(aConnectionFailed) @@ -1093,17 +1097,18 @@ protected: ErrorResult& mRv; bool* mConnectionFailed; }; class AsyncOpenRunnable final : public WebSocketMainThreadRunnable { public: AsyncOpenRunnable(WebSocketImpl* aImpl, ErrorResult& aRv) - : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate) + : WebSocketMainThreadRunnable(aImpl->mWorkerPrivate, + NS_LITERAL_CSTRING("WebSocket :: AsyncOpen")) , mImpl(aImpl) , mRv(aRv) { MOZ_ASSERT(mWorkerPrivate); mWorkerPrivate->AssertIsOnWorkerThread(); } protected:
--- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -3061,47 +3061,35 @@ nsDOMWindowUtils::RemoteFrameFullscreenR { nsCOMPtr<nsIDocument> doc = GetDocument(); NS_ENSURE_STATE(doc); doc->RemoteFrameFullscreenReverted(); return NS_OK; } -class MOZ_STACK_CLASS FullscreenChangePrepare +static void +PrepareForFullscreenChange(nsIPresShell* aPresShell, const nsSize& aSize, + nsSize* aOldSize = nullptr) { -public: - FullscreenChangePrepare(nsIPresShell* aPresShell, - const nsSize& aSize, nsSize* aOldSize = nullptr) - : mPresShell(aPresShell) - { - if (mPresShell) { - mPresShell->SetIsInFullscreenChange(true); - } - if (aSize.IsEmpty()) { - return; - } - if (nsViewManager* viewManager = mPresShell->GetViewManager()) { + if (!aPresShell) { + return; + } + if (nsRefreshDriver* rd = aPresShell->GetRefreshDriver()) { + rd->SetIsResizeSuppressed(); + } + if (!aSize.IsEmpty()) { + if (nsViewManager* viewManager = aPresShell->GetViewManager()) { if (aOldSize) { viewManager->GetWindowDimensions(&aOldSize->width, &aOldSize->height); } viewManager->SetWindowDimensions(aSize.width, aSize.height); } } - - ~FullscreenChangePrepare() - { - if (mPresShell) { - mPresShell->SetIsInFullscreenChange(false); - } - } - -private: - nsCOMPtr<nsIPresShell> mPresShell; -}; +} class OldWindowSize : public LinkedListElement<OldWindowSize> { public: static void Set(nsPIDOMWindowOuter* aWindow, const nsSize& aSize) { OldWindowSize* item = GetItem(aWindow); if (item) { @@ -3152,53 +3140,55 @@ private: nsSize mSize; }; LinkedList<OldWindowSize> OldWindowSize::sList; NS_IMETHODIMP nsDOMWindowUtils::HandleFullscreenRequests(bool* aRetVal) { + PROFILER_MARKER("Enter fullscreen"); nsCOMPtr<nsIDocument> doc = GetDocument(); NS_ENSURE_STATE(doc); // Notify the pres shell that we are starting fullscreen change, and // set the window dimensions in advance. Since the resize message // comes after the fullscreen change call, doing so could avoid an // extra resize reflow after this point. nsRect screenRect; if (nsPresContext* presContext = GetPresContext()) { presContext->DeviceContext()->GetRect(screenRect); } nsSize oldSize; - FullscreenChangePrepare prepare(GetPresShell(), screenRect.Size(), &oldSize); + PrepareForFullscreenChange(GetPresShell(), screenRect.Size(), &oldSize); OldWindowSize::Set(doc->GetWindow(), oldSize); *aRetVal = nsIDocument::HandlePendingFullscreenRequests(doc); return NS_OK; } nsresult nsDOMWindowUtils::ExitFullscreen() { + PROFILER_MARKER("Exit fullscreen"); nsCOMPtr<nsIDocument> doc = GetDocument(); NS_ENSURE_STATE(doc); // Although we would not use the old size if we have already exited // fullscreen, we still want to cleanup in case we haven't. nsSize oldSize = OldWindowSize::GetAndRemove(doc->GetWindow()); if (!doc->GetFullscreenElement()) { return NS_OK; } // Notify the pres shell that we are starting fullscreen change, and // set the window dimensions in advance. Since the resize message // comes after the fullscreen change call, doing so could avoid an // extra resize reflow after this point. - FullscreenChangePrepare prepare(GetPresShell(), oldSize); + PrepareForFullscreenChange(GetPresShell(), oldSize); nsIDocument::ExitFullscreenInDocTree(doc); return NS_OK; } NS_IMETHODIMP nsDOMWindowUtils::SelectAtPoint(float aX, float aY, uint32_t aSelectBehavior, bool *_retval) {
--- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -7,16 +7,17 @@ #include "nsGlobalWindow.h" #include <algorithm> #include "mozilla/MemoryReporting.h" // Local Includes #include "Navigator.h" +#include "nsContentSecurityManager.h" #include "nsScreen.h" #include "nsHistory.h" #include "nsPerformance.h" #include "nsDOMNavigationTiming.h" #include "nsIDOMStorageManager.h" #include "mozilla/dom/DOMStorage.h" #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEventBinding.h" @@ -1174,16 +1175,17 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW mAddActiveEventFuzzTime(true), mIsFrozen(false), mFullScreen(false), mFullscreenMode(false), mIsClosed(false), mInClose(false), mHavePendingClose(false), mHadOriginalOpener(false), + mOriginalOpenerWasSecureContext(false), mIsPopupSpam(false), mBlockScriptedClosingFlag(false), mWasOffline(false), mHasHadSlowScript(false), mNotifyIdleObserversIdleOnThaw(false), mNotifyIdleObserversActiveOnThaw(false), mCreatingInnerWindow(false), mIsChrome(false), @@ -2341,26 +2343,202 @@ InitializeLegacyNetscapeObject(JSContext /* Define PrivilegeManager object with the necessary "static" methods. */ obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr); NS_ENSURE_TRUE(obj, false); return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec); } /** + * Returns true if the "HTTPS state" of the document should be "modern". See: + * + * https://html.spec.whatwg.org/#concept-document-https-state + * https://fetch.spec.whatwg.org/#concept-response-https-state + * + * Note: this function only relates to figuring out HTTPS state, which is an + * input to the Secure Context algorithm. We are not actually implementing any + * part of the Secure Context algorithm itself here. + * + * This is a bit of a hack. Ideally we'd propagate HTTPS state through + * nsIChannel as described in the Fetch and HTML specs, but making channels + * know about whether they should inherit HTTPS state, propagating information + * about who the channel's "client" is, exposing GetHttpsState API on channels + * and modifying the various cache implementations to store and retrieve HTTPS + * state involves a huge amount of code (see bug 1220687). We avoid that for + * now using this function. + * + * This function takes advantage of the observation that we can return true if + * nsIContentSecurityManager::IsOriginPotentiallyTrustworthy returns true for + * the document's origin (e.g. the origin has a scheme of 'https' or host + * 'localhost' etc.). Since we generally propagate a creator document's origin + * onto data:, blob:, etc. documents, this works for them too. + * + * The scenario where this observation breaks down is sandboxing without the + * 'allow-same-origin' flag, since in this case a document is given a unique + * origin (IsOriginPotentiallyTrustworthy would return false). We handle that + * by using the origin that the document would have had had it not been + * sandboxed. + * + * DEFICIENCIES: Note that this function uses nsIScriptSecurityManager's + * getChannelResultPrincipalIfNotSandboxed, and that method's ignoring of + * sandboxing is limited to the immediate sandbox. In the case that aDocument + * should inherit its origin (e.g. data: URI) but its parent has ended up + * with a unique origin due to sandboxing further up the parent chain we may + * end up returning false when we would ideally return true (since we will + * examine the parent's origin for 'https' and not finding it.) This means + * that we may restrict the privileges of some pages unnecessarily in this + * edge case. + */ +static bool HttpsStateIsModern(nsIDocument* aDocument) +{ + nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal(); + + if (principal->GetIsSystemPrincipal()) { + return true; + } + + // If aDocument is sandboxed, try and get the principal that it would have + // been given had it not been sandboxed: + if (principal->GetIsNullPrincipal() && + (aDocument->GetSandboxFlags() & SANDBOXED_ORIGIN)) { + nsIChannel* channel = aDocument->GetChannel(); + if (channel) { + nsCOMPtr<nsIScriptSecurityManager> ssm = + nsContentUtils::GetSecurityManager(); + nsresult rv = + ssm->GetChannelResultPrincipalIfNotSandboxed(channel, + getter_AddRefs(principal)); + if (NS_FAILED(rv)) { + return false; + } + if (principal->GetIsSystemPrincipal()) { + // If a document with the system principal is sandboxing a subdocument + // that would normally inherit the embedding element's principal (e.g. + // a srcdoc document) then the embedding document does not trust the + // content that is written to the embedded document. Unlike when the + // embedding document is https, in this case we have no indication as + // to whether the embedded document's contents are delivered securely + // or not, and the sandboxing would possibly indicate that they were + // not. To play it safe we return false here. (See bug 1162772 + // comment 73-80.) + return false; + } + } + } + + if (principal->GetIsNullPrincipal()) { + return false; + } + + MOZ_ASSERT(principal->GetIsCodebasePrincipal()); + + nsCOMPtr<nsIContentSecurityManager> csm = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + NS_WARN_IF(!csm); + if (csm) { + bool isTrustworthyOrigin = false; + csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin); + if (isTrustworthyOrigin) { + return true; + } + } + + return false; +} + +bool +nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument) +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal(); + if (nsContentUtils::IsSystemPrincipal(principal)) { + return true; + } + + // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object + + bool hadNonSecureContextCreator = false; + + nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent(); + MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?"); + if (nsGlobalWindow::Cast(parentOuterWin) != this) { + // There may be a small chance that parentOuterWin has navigated in + // the time that it took us to start loading this sub-document. If that + // were the case then parentOuterWin->GetCurrentInnerWindow() wouldn't + // return the window for the document that is embedding us. For this + // reason we only use the GetScriptableParent call above to check that we + // have a same-type parent, but actually get the inner window via the + // document that we know is embedding us. + nsIDocument* creatorDoc = aDocument->GetParentDocument(); + if (!creatorDoc) { + return false; // we must be tearing down + } + nsGlobalWindow* parentWin = + nsGlobalWindow::Cast(creatorDoc->GetInnerWindow()); + if (!parentWin) { + return false; // we must be tearing down + } + MOZ_ASSERT(parentWin == + nsGlobalWindow::Cast(parentOuterWin->GetCurrentInnerWindow()), + "Creator window mismatch while setting Secure Context state"); + hadNonSecureContextCreator = !parentWin->IsSecureContext(); + } else if (mHadOriginalOpener) { + hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext; + } + + if (hadNonSecureContextCreator) { + return false; + } + + if (HttpsStateIsModern(aDocument)) { + return true; + } + + if (principal->GetIsNullPrincipal()) { + nsCOMPtr<nsIURI> uri = aDocument->GetOriginalURI(); + // IsOriginPotentiallyTrustworthy doesn't care about origin attributes so + // it doesn't actually matter what we use here, but reusing the document + // principal's attributes is convenient. + const PrincipalOriginAttributes& attrs = + BasePrincipal::Cast(principal)->OriginAttributesRef(); + // CreateCodebasePrincipal correctly gets a useful principal for blob: and + // other URI_INHERITS_SECURITY_CONTEXT URIs. + principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); + if (NS_WARN_IF(!principal)) { + return false; + } + } + + nsCOMPtr<nsIContentSecurityManager> csm = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + NS_WARN_IF(!csm); + if (csm) { + bool isTrustworthyOrigin = false; + csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin); + if (isTrustworthyOrigin) { + return true; + } + } + + return false; +} + +/** * Create a new global object that will be used for an inner window. * Return the native global and an nsISupports 'holder' that can be used * to manage the lifetime of it. */ static nsresult CreateNativeGlobalForInner(JSContext* aCx, nsGlobalWindow* aNewInner, nsIURI* aURI, nsIPrincipal* aPrincipal, - JS::MutableHandle<JSObject*> aGlobal) + JS::MutableHandle<JSObject*> aGlobal, + bool aIsSecureContext) { MOZ_ASSERT(aCx); MOZ_ASSERT(aNewInner); MOZ_ASSERT(aNewInner->IsInnerWindow()); MOZ_ASSERT(aPrincipal); // DOMWindow with nsEP is not supported, we have to make sure // no one creates one accidentally. @@ -2380,16 +2558,18 @@ CreateNativeGlobalForInner(JSContext* aC if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { options.creationOptions().setAddonId(MapURIToAddonID(aURI)); } if (top && top->GetGlobalJSObject()) { options.creationOptions().setSameZoneAs(top->GetGlobalJSObject()); } + options.creationOptions().setSecureContext(aIsSecureContext); + xpc::InitGlobalObjectOptions(options, aPrincipal); // Determine if we need the Components object. bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) || TreatAsRemoteXUL(aPrincipal); uint32_t flags = needComponents ? 0 : nsIXPConnect::OMIT_COMPONENTS_OBJECT; flags |= nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK; @@ -2598,17 +2778,18 @@ nsGlobalWindow::SetNewDocument(nsIDocume Freeze(); mCreatingInnerWindow = true; // Every script context we are initialized with must create a // new global. rv = CreateNativeGlobalForInner(cx, newInnerWindow, aDocument->GetDocumentURI(), aDocument->NodePrincipal(), - &newInnerGlobal); + &newInnerGlobal, + ComputeIsSecureContext(aDocument)); NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal && newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal, "Failed to get script global"); mCreatingInnerWindow = false; createdInnerWindow = true; Thaw(); @@ -3039,17 +3220,21 @@ nsGlobalWindow::SetOpenerWindow(nsPIDOMW "SetOpenerWindow!"); NS_ASSERTION(aOpener || !aOriginalOpener, "Shouldn't set mHadOriginalOpener if aOpener is null"); mOpener = do_GetWeakReference(aOpener); NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!"); if (aOriginalOpener) { + MOZ_ASSERT(!mHadOriginalOpener, + "Probably too late to call ComputeIsSecureContext again"); mHadOriginalOpener = true; + mOriginalOpenerWasSecureContext = + nsGlobalWindow::Cast(aOpener->GetCurrentInnerWindow())->IsSecureContext(); } #ifdef DEBUG mSetOpenerWindowCalled = true; #endif } static @@ -3372,16 +3557,17 @@ nsGlobalWindow::PostHandleEvent(EventCha if (element && GetParentInternal() && docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { // If we're not in chrome, or at a chrome boundary, fire the // onload event for the frame element. nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad); event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; // Most of the time we could get a pres context to pass in here, // but not always (i.e. if this window is not shown there won't // be a pres context available). Since we're not firing a GUI // event we don't need a pres context anyway so we just pass // null as the pres context all the time here. EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); } @@ -5979,28 +6165,51 @@ public: NS_IMETHOD Run() override; private: virtual ~FullscreenTransitionTask() { MOZ_COUNT_DTOR(FullscreenTransitionTask); } + /** + * The flow of fullscreen transition: + * + * parent process | child process + * ---------------------------------------------------------------- + * + * | request/exit fullscreen + * <-----| + * BeforeToggle stage | + * | + * ToggleFullscreen stage *1 |-----> + * | HandleFullscreenRequests + * | + * <-----| MozAfterPaint event + * AfterToggle stage *2 | + * | + * End stage | + * + * Note we also start a timer at *1 so that if we don't get MozAfterPaint + * from the child process in time, we continue going to *2. + */ enum Stage { // BeforeToggle stage happens before we enter or leave fullscreen // state. In this stage, the task triggers the pre-toggle fullscreen // transition on the widget. eBeforeToggle, // ToggleFullscreen stage actually executes the fullscreen toggle, // and wait for the next paint on the content to continue. eToggleFullscreen, // AfterToggle stage happens after we toggle the fullscreen state. // In this stage, the task triggers the post-toggle fullscreen // transition on the widget. - eAfterToggle + eAfterToggle, + // End stage is triggered after the final transition finishes. + eEnd }; class Observer final : public nsIObserver { public: NS_DECL_ISUPPORTS NS_DECL_NSIOBSERVER @@ -6036,20 +6245,22 @@ FullscreenTransitionTask::Run() mStage = Stage(mStage + 1); if (MOZ_UNLIKELY(mWidget->Destroyed())) { // If the widget has been destroyed before we get here, don't try to // do anything more. Just let it go and release ourselves. NS_WARNING("The widget to fullscreen has been destroyed"); return NS_OK; } if (stage == eBeforeToggle) { + PROFILER_MARKER("Fullscreen transition start"); mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle, mDuration.mFadeIn, mTransitionData, this); } else if (stage == eToggleFullscreen) { + PROFILER_MARKER("Fullscreen toggle start"); if (MOZ_UNLIKELY(mWindow->mFullScreen != mFullscreen)) { // This could happen in theory if several fullscreen requests in // different direction happen continuously in a short time. We // need to ensure the fullscreen state matches our target here, // otherwise the widget would change the window state as if we // toggle for Fullscreen Mode instead of Fullscreen API. NS_WARNING("The fullscreen state of the window does not match"); mWindow->mFullScreen = mFullscreen; @@ -6079,16 +6290,18 @@ FullscreenTransitionTask::Run() mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); uint32_t timeout = Preferences::GetUint("full-screen-api.transition.timeout", 500); mTimer->Init(observer, timeout, nsITimer::TYPE_ONE_SHOT); } else if (stage == eAfterToggle) { mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle, mDuration.mFadeOut, mTransitionData, this); + } else if (stage == eEnd) { + PROFILER_MARKER("Fullscreen transition end"); } return NS_OK; } NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver) NS_IMETHODIMP FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject, @@ -6099,26 +6312,28 @@ FullscreenTransitionTask::Observer::Obse if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) { nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject)); nsCOMPtr<nsIWidget> widget = win ? nsGlobalWindow::Cast(win)->GetMainWidget() : nullptr; if (widget == mTask->mWidget) { // The paint notification arrives first. Cancel the timer. mTask->mTimer->Cancel(); shouldContinue = true; + PROFILER_MARKER("Fullscreen toggle end"); } } else { #ifdef DEBUG MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0, "Should only get fullscreen-painted or timer-callback"); nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject)); MOZ_ASSERT(timer && timer == mTask->mTimer, "Should only trigger this with the timer the task created"); #endif shouldContinue = true; + PROFILER_MARKER("Fullscreen toggle timeout"); } if (shouldContinue) { nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); obs->RemoveObserver(this, kPaintedTopic); mTask->mTimer = nullptr; mTask->Run(); } return NS_OK; @@ -6241,38 +6456,41 @@ nsGlobalWindow::SetFullscreenInternal(Fu if (MakeWidgetFullscreen(this, aHMD, aReason, aFullScreen)) { // The rest of code for switching fullscreen is in nsGlobalWindow:: // FinishFullscreenChange() which will be called after sizemodechange // event is dispatched. return NS_OK; } } - // If we didn't setup the widget, we may need to manually set this - // flag, or the assertion in FinishFullscreenChange is violated. - if (nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell()) { - if (!presShell->IsInFullscreenChange()) { - presShell->SetIsInFullscreenChange(true); - } - } FinishFullscreenChange(aFullScreen); return NS_OK; } bool nsGlobalWindow::SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen, nsIWidget* aWidget, nsIScreen* aScreen) { MOZ_ASSERT(IsOuterWindow()); MOZ_ASSERT(this == GetTopInternal(), "Only topmost window should call this"); MOZ_ASSERT(!AsOuter()->GetFrameElementInternal(), "Content window should not call this"); MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); - if (nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell()) { - presShell->SetIsInFullscreenChange(true); + if (!NS_WARN_IF(!IsChromeWindow())) { + auto chromeWin = static_cast<nsGlobalChromeWindow*>(this); + if (!NS_WARN_IF(chromeWin->mFullscreenPresShell)) { + if (nsIPresShell* shell = mDocShell->GetPresShell()) { + if (nsRefreshDriver* rd = shell->GetRefreshDriver()) { + chromeWin->mFullscreenPresShell = do_GetWeakReference(shell); + MOZ_ASSERT(chromeWin->mFullscreenPresShell); + rd->SetIsResizeSuppressed(); + rd->Freeze(); + } + } + } } nsresult rv = aReason == FullscreenReason::ForFullscreenMode ? // If we enter fullscreen for fullscreen mode, we want // the native system behavior. aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen, aScreen) : aWidget->MakeFullScreen(aIsFullscreen, aScreen); return NS_SUCCEEDED(rv); } @@ -6304,19 +6522,25 @@ nsGlobalWindow::FinishFullscreenChange(b // that the chrome can distinguish between browser fullscreen mode // and DOM fullscreen. FinishDOMFullscreenChange(mDoc, mFullScreen); // dispatch a "fullscreen" DOM event so that XUL apps can // respond visually if we are kicked into full screen mode DispatchCustomEvent(NS_LITERAL_STRING("fullscreen")); - if (nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell()) { - MOZ_ASSERT(presShell->IsInFullscreenChange()); - presShell->SetIsInFullscreenChange(false); + if (!NS_WARN_IF(!IsChromeWindow())) { + auto chromeWin = static_cast<nsGlobalChromeWindow*>(this); + if (nsCOMPtr<nsIPresShell> shell = + do_QueryReferent(chromeWin->mFullscreenPresShell)) { + if (nsRefreshDriver* rd = shell->GetRefreshDriver()) { + rd->Thaw(); + } + chromeWin->mFullscreenPresShell = nullptr; + } } if (!mWakeLock && mFullScreen) { RefPtr<power::PowerManagerService> pmService = power::PowerManagerService::GetInstance(); if (!pmService) { return; } @@ -13870,16 +14094,24 @@ nsGlobalWindow::GetConsole(ErrorResult& if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } return mConsole; } +bool +nsGlobalWindow::IsSecureContext() const +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + return JS_GetIsSecureContext(js::GetObjectCompartment(GetWrapperPreserveColor())); +} + already_AddRefed<External> nsGlobalWindow::GetExternal(ErrorResult& aRv) { MOZ_RELEASE_ASSERT(IsInnerWindow()); #ifdef HAVE_SIDEBAR if (!mExternal) { AutoJSContext cx;
--- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -919,16 +919,19 @@ public: already_AddRefed<nsIDOMOfflineResourceList> GetApplicationCache() override; #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) int16_t Orientation() const; #endif mozilla::dom::Console* GetConsole(mozilla::ErrorResult& aRv); + // https://w3c.github.io/webappsec-secure-contexts/#dom-window-issecurecontext + bool IsSecureContext() const; + void GetSidebar(mozilla::dom::OwningExternalOrWindowProxy& aResult, mozilla::ErrorResult& aRv); already_AddRefed<mozilla::dom::External> GetExternal(mozilla::ErrorResult& aRv); // Exposed only for testing static bool TokenizeDialogOptions(nsAString& aToken, nsAString::const_iterator& aIter, nsAString::const_iterator aEnd); @@ -1659,16 +1662,20 @@ protected: void CheckForDPIChange(); private: // Fire the JS engine's onNewGlobalObject hook. Only used on inner windows. void FireOnNewGlobalObject(); void DisconnectEventTargetObjects(); + // Called only on outer windows to compute the value that will be returned by + // IsSecureContext() for the inner window that corresponds to aDocument. + bool ComputeIsSecureContext(nsIDocument* aDocument); + protected: // This member is also used on both inner and outer windows, but // for slightly different purposes. On inner windows it means the // inner window is held onto by session history and should not // change. On outer windows it means that the window is in a state // where we don't want to force creation of a new inner window since // we're in the middle of doing just that. bool mIsFrozen : 1; @@ -1679,16 +1686,17 @@ protected: bool mFullscreenMode : 1; bool mIsClosed : 1; bool mInClose : 1; // mHavePendingClose means we've got a termination function set to // close us when the JS stops executing or that we have a close // event posted. If this is set, just ignore window.close() calls. bool mHavePendingClose : 1; bool mHadOriginalOpener : 1; + bool mOriginalOpenerWasSecureContext : 1; bool mIsPopupSpam : 1; // Indicates whether scripts are allowed to close this window. bool mBlockScriptedClosingFlag : 1; // Window offline status. Checked to see if we need to fire offline event bool mWasOffline : 1; @@ -1966,16 +1974,20 @@ public: using nsGlobalWindow::NotifyDefaultButtonLoaded; using nsGlobalWindow::GetMessageManager; using nsGlobalWindow::GetGroupMessageManager; using nsGlobalWindow::BeginWindowMove; nsCOMPtr<nsIBrowserDOMWindow> mBrowserDOMWindow; nsCOMPtr<nsIMessageBroadcaster> mMessageManager; nsInterfaceHashtable<nsStringHashKey, nsIMessageBroadcaster> mGroupMessageManagers; + // A weak pointer to the nsPresShell that we are doing fullscreen for. + // The pointer being set indicates we've set the IsInFullscreenChange + // flag on this pres shell. + nsWeakPtr mFullscreenPresShell; }; /* * nsGlobalModalWindow inherits from nsGlobalWindow. It is the global * object created for a modal content windows only (i.e. not modal * chrome dialogs). */ class nsGlobalModalWindow : public nsGlobalWindow,
--- a/dom/base/nsObjectLoadingContent.cpp +++ b/dom/base/nsObjectLoadingContent.cpp @@ -6,16 +6,17 @@ /* * A base class implementing nsIObjectLoadingContent for use by * various content nodes that want to provide plugin/document/image * loading functionality (eg <embed>, <object>, <applet>, etc). */ // Interface headers #include "imgLoader.h" +#include "nsIConsoleService.h" #include "nsIContent.h" #include "nsIContentInlines.h" #include "nsIDocShell.h" #include "nsIDocument.h" #include "nsIDOMCustomEvent.h" #include "nsIDOMDocument.h" #include "nsIDOMHTMLObjectElement.h" #include "nsIDOMHTMLAppletElement.h" @@ -81,16 +82,18 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/Event.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/PluginCrashedEvent.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStates.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/HTMLObjectElementBinding.h" +#include "mozilla/dom/HTMLSharedObjectElement.h" +#include "nsChannelClassifier.h" #ifdef XP_WIN // Thanks so much, Microsoft! :( #ifdef CreateEvent #undef CreateEvent #endif #endif // XP_WIN @@ -100,16 +103,17 @@ // maybe on nsIObjectLoadingContext. #include "mozilla/dom/HTMLObjectElement.h" #endif static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); static const char *kPrefJavaMIME = "plugin.java.mime"; static const char *kPrefYoutubeRewrite = "plugins.rewrite_youtube_embeds"; +static const char *kPrefBlockURIs = "browser.safebrowsing.blockedURIs.enabled"; using namespace mozilla; using namespace mozilla::dom; static LogModule* GetObjectLog() { static LazyLogModule sLog("objlc"); @@ -1148,30 +1152,16 @@ nsObjectLoadingContent::OnStopRequest(ns // We make a note of this object node by including it in a dedicated // array of blocked tracking nodes under its parent document. if (aStatusCode == NS_ERROR_TRACKING_URI) { nsCOMPtr<nsIContent> thisNode = do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this)); if (thisNode && thisNode->IsInComposedDoc()) { thisNode->GetComposedDoc()->AddBlockedTrackingNode(thisNode); } - } else if (aStatusCode == NS_ERROR_BLOCKED_URI) { - // Logging is temporarily disabled until after experiment phase. - // - // nsAutoCString uri; - // mURI->GetSpec(uri); - // nsCOMPtr<nsIConsoleService> console( - // do_GetService("@mozilla.org/consoleservice;1")); - // if (console) { - // nsString message = NS_LITERAL_STRING("Blocking ") + - // NS_ConvertASCIItoUTF16(uri) + - // NS_LITERAL_STRING(" since it was found on an internal Firefox blocklist."); - // console->LogStringMessage(message.get()); - // } - Telemetry::Accumulate(Telemetry::PLUGIN_BLOCKED_FOR_STABILITY, 1); } NS_ENSURE_TRUE(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), NS_ERROR_NOT_AVAILABLE); if (aRequest != mChannel) { return NS_BINDING_ABORTED; } @@ -2281,16 +2271,42 @@ nsObjectLoadingContent::LoadObject(bool } // If we're loading a type now, check ProcessPolicy. Note that we may check // both now in the case of plugins whose type is determined before opening a // channel. if (allowLoad && mType != eType_Loading) { allowLoad = CheckProcessPolicy(&contentPolicy); } + // This needs to be reverted once the plugin stability experiment is over (see bug #1268120). + if (allowLoad && Preferences::GetBool(kPrefBlockURIs)) { + RefPtr<nsChannelClassifier> channelClassifier = new nsChannelClassifier(); + nsCOMPtr<nsIURIClassifier> classifier = do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID); + if (classifier) { + nsAutoCString tables; + Preferences::GetCString("urlclassifier.blockedTable", &tables); + nsAutoCString results; + rv = classifier->ClassifyLocalWithTables(mURI, tables, results); + if (NS_SUCCEEDED(rv) && !results.IsEmpty()) { + nsAutoCString uri; + mURI->GetSpec(uri); + nsCOMPtr<nsIConsoleService> console( + do_GetService("@mozilla.org/consoleservice;1")); + if (console) { + nsString message = NS_LITERAL_STRING("Blocking ") + + NS_ConvertASCIItoUTF16(uri) + + NS_LITERAL_STRING(" since it was found on an internal Firefox blocklist."); + console->LogStringMessage(message.get()); + } + Telemetry::Accumulate(Telemetry::PLUGIN_BLOCKED_FOR_STABILITY, 1); + allowLoad = false; + } + } + } + // Content policy implementations can mutate the DOM, check for re-entry if (!mIsLoading) { LOG(("OBJLC [%p]: We re-entered in content policy, leaving original load", this)); return NS_OK; } // Load denied, switch to fallback and set disabled/suppressed if applicable @@ -3088,34 +3104,45 @@ nsObjectLoadingContent::LoadFallback(Fal NS_ASSERTION(thisContent, "must be a content"); if (!thisContent->IsHTMLElement() || mContentType.IsEmpty()) { // Don't let custom fallback handlers run outside HTML, tags without a // determined type should always just be alternate content aType = eFallbackAlternate; } + // We'll set this to null no matter what now, doing it here means we'll load + // child embeds as we find them in the upcoming loop. + mType = eType_Null; + + // Do a breadth-first traverse of node tree with the current element as root, + // looking for the first embed we can find. + nsTArray<nsINodeList*> childNodes; if ((thisContent->IsHTMLElement(nsGkAtoms::object) || thisContent->IsHTMLElement(nsGkAtoms::applet)) && (aType == eFallbackUnsupported || aType == eFallbackDisabled || aType == eFallbackBlocklisted)) { - // Show alternate content instead, if it exists - for (nsIContent* child = thisContent->GetFirstChild(); - child; child = child->GetNextSibling()) { - if (!child->IsHTMLElement(nsGkAtoms::param) && + for (nsIContent* child = thisContent->GetFirstChild(); child; + child = child->GetNextNode(thisContent)) { + if (aType != eFallbackAlternate && + !child->IsHTMLElement(nsGkAtoms::param) && nsStyleUtil::IsSignificantChild(child, true, false)) { aType = eFallbackAlternate; - break; + } + if (child->IsHTMLElement(nsGkAtoms::embed)) { + HTMLSharedObjectElement* object = static_cast<HTMLSharedObjectElement*>(child); + if (object) { + object->StartObjectLoad(true, true); + } } } } - mType = eType_Null; mFallbackType = aType; // Notify if (!aNotify) { return; // done } NotifyStateChanged(oldType, oldState, false, true);
new file mode 100644 --- /dev/null +++ b/dom/base/test/file_bug1263696_frame_fail.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/REC-html401-19991224/strict.dtd"> +<html> + <head> + <title>Bug 1263696 - iframe that should not be loaded</title> + </head> + <body> + <script> + parent.SimpleTest.ok(false, "this iframe should not load"); + </script> + </body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/base/test/file_bug1263696_frame_pass.html @@ -0,0 +1,13 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" + "http://www.w3.org/TR/REC-html401-19991224/strict.dtd"> +<html> + <head> + <title>Bug 1263696 - iframe that should be loaded</title> + </head> + <body> + <script> + parent.index = parent.index + 1; + parent.SimpleTest.ok(true, "this iframe should load"); + </script> + </body> +</html>
--- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -152,16 +152,18 @@ support-files = file_bug787778.sjs file_bug804395.jar file_bug869432.eventsource file_bug869432.eventsource^headers^ file_bug902350.html file_bug902350_frame.html file_bug907892.html file_bug945152.jar + file_bug1263696_frame_pass.html + file_bug1263696_frame_fail.html file_general_document.html file_html_in_xhr.html file_html_in_xhr.sjs file_html_in_xhr2.html file_html_in_xhr3.html file_htmlserializer_1.html file_htmlserializer_1_bodyonly.html file_htmlserializer_1_format.html @@ -889,8 +891,9 @@ skip-if = buildapp == 'b2g' #no ssl supp [test_bug769117.html] [test_bug1250148.html] [test_bug1240471.html] [test_mozbrowser_apis_allowed.html] [test_mozbrowser_apis_blocked.html] [test_document_register.html] [test_bug962251.html] [test_bug1259588.html] +[test_bug1263696.html]
--- a/dom/base/test/test_EventSource_redirects.html +++ b/dom/base/test/test_EventSource_redirects.html @@ -16,19 +16,16 @@ https://bugzilla.mozilla.org/show_bug.cg <p id="display"></p> <div id="content" style="display: none"> </div> <pre id="test"> <script class="testbody" type="text/javascript"> function doTest(test_id) { - oldPrefVal = SpecialPowers.getBoolPref("dom.server-events.enabled"); - ok(true, "here we go"); - source = new EventSource("eventsource_redirect.resource"); ok(source.url == "http://mochi.test:8888/tests/dom/base/test/eventsource_redirect.resource", "Test failed."); ok(source.readyState == 0 || source.readyState == 1, "Test failed."); source.onopen = function (event) { ok(true, "opened"); }; @@ -42,17 +39,15 @@ https://bugzilla.mozilla.org/show_bug.cg ok(false, "received onError: " + event); source.close(); SimpleTest.finish(); }; } SimpleTest.waitForExplicitFinish(); - addLoadEvent(function() { - SpecialPowers.pushPrefEnv({"set": [['dom.server-events.enabled', true]]}, doTest); - }); + addLoadEvent(doTest); </script> </pre> </body> </html>
new file mode 100644 --- /dev/null +++ b/dom/base/test/test_bug1263696.html @@ -0,0 +1,53 @@ +<!DOCTYPE html> +<html> + <head> + <meta><charset="utf-8"/> + <title>Test Embed/Object Node Conflicts</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="plugin-utils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + var index = 0; + function startTest() { + is(index, 12, "Should have loaded all passing frames."); + SimpleTest.finish(); + } + </script> + </head> + <body onload="startTest()"> + <object data="file_bug1263696_frame_pass.html" style="width: 100px; height: 100px"> + <embed type="text/html" src="file_bug1263696_frame_fail.html" /> + </object> + <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test"> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + </object> + <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test"> + <div></div> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + </object> + <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test"> + <div> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + </div> + </object> + <object style="width: 100px; height: 100px" data="data:application/x-does-not-exist,test"> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + <object data="file_bug1263696_frame_pass.html"> + <embed type="text/html" src="file_bug1263696_frame_fail.html" /> + </object> + <object data="data:application/x-does-not-exist,test"> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + </object> + </object> + <div> + <object data="file_bug1263696_frame_pass.html" style="width: 100px; height: 100px"></object> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + </div> + <div> + <embed type="text/html" src="file_bug1263696_frame_pass.html" /> + <object data="file_bug1263696_frame_pass.html" style="width: 100px; height: 100px"></object> + </div> + </body> +</html>
--- a/dom/base/test/test_bug338583.html +++ b/dom/base/test/test_bug338583.html @@ -616,17 +616,21 @@ https://bugzilla.mozilla.org/show_bug.cg document.getElementById('waitSpan').innerHTML = ''; setTestHasFinished(test_id); }, parseInt(8000*stress_factor)); } function doTest() { // Allow all cookies, then run the actual test - SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0], ["dom.server-events.enabled", true]]}, function() { SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], doTestCallback);}); + SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, + function() { + SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], + doTestCallback); + }); } function doTestCallback() { // we get a good stress_factor by testing 10 setTimeouts and some float // arithmetic taking my machine as stress_factor==1 (time=589)
--- a/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.cpp +++ b/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.cpp @@ -83,26 +83,27 @@ BluetoothDaemonSocketModule::ConnectCmd( } Unused << pdu.release(); return rv; } /* |DeleteTask| deletes a class instance on the I/O thread */ template <typename T> -class DeleteTask final : public Task +class DeleteTask final : public Runnable { public: DeleteTask(T* aPtr) : mPtr(aPtr) { } - void Run() override + NS_IMETHOD Run() override { mPtr = nullptr; + return NS_OK; } private: UniquePtr<T> mPtr; }; /* |AcceptWatcher| specializes SocketMessageWatcher for Accept * operations by reading the socket messages from Bluedroid and @@ -129,41 +130,41 @@ public: GetConnectionStatus())); } else { ErrorRunnable::Dispatch(GetResultHandler(), &BluetoothSocketResultHandler::OnError, ConstantInitOp1<BluetoothStatus>(aStatus)); } MessageLoopForIO::current()->PostTask( - FROM_HERE, new DeleteTask<AcceptWatcher>(this)); + MakeAndAddRef<DeleteTask<AcceptWatcher>>(this)); } }; nsresult BluetoothDaemonSocketModule::AcceptCmd(int aFd, BluetoothSocketResultHandler* aRes) { MOZ_ASSERT(NS_IsMainThread()); /* receive Bluedroid's socket-setup messages and client fd */ - Task* t = new SocketMessageWatcherTask(new AcceptWatcher(aFd, aRes)); - XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t); + XRE_GetIOMessageLoop()->PostTask( + MakeAndAddRef<SocketMessageWatcherTask>(new AcceptWatcher(aFd, aRes))); return NS_OK; } nsresult BluetoothDaemonSocketModule::CloseCmd(BluetoothSocketResultHandler* aRes) { MOZ_ASSERT(NS_IsMainThread()); /* stop the watcher corresponding to |aRes| */ - Task* t = new DeleteSocketMessageWatcherTask(aRes); - XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t); + XRE_GetIOMessageLoop()->PostTask( + MakeAndAddRef<DeleteSocketMessageWatcherTask>(aRes)); return NS_OK; } void BluetoothDaemonSocketModule::HandleSvc(const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, DaemonSocketResultHandler* aRes) @@ -268,17 +269,17 @@ public: GetConnectionStatus())); } else { ErrorRunnable::Dispatch(GetResultHandler(), &BluetoothSocketResultHandler::OnError, ConstantInitOp1<BluetoothStatus>(aStatus)); } MessageLoopForIO::current()->PostTask( - FROM_HERE, new DeleteTask<ConnectWatcher>(this)); + MakeAndAddRef<DeleteTask<ConnectWatcher>>(this)); } }; void BluetoothDaemonSocketModule::ConnectRsp(const DaemonSocketPDUHeader& aHeader, DaemonSocketPDU& aPDU, BluetoothSocketResultHandler* aRes) { @@ -293,18 +294,18 @@ BluetoothDaemonSocketModule::ConnectRsp( fd = receiveFds[0]; if (fd < 0) { ErrorRunnable::Dispatch(aRes, &BluetoothSocketResultHandler::OnError, ConstantInitOp1<BluetoothStatus>(STATUS_FAIL)); return; } /* receive Bluedroid's socket-setup messages */ - Task* t = new SocketMessageWatcherTask(new ConnectWatcher(fd, aRes)); - XRE_GetIOMessageLoop()->PostTask(FROM_HERE, t); + XRE_GetIOMessageLoop()->PostTask( + MakeAndAddRef<SocketMessageWatcherTask>(new ConnectWatcher(fd, aRes))); } // // Socket interface // BluetoothDaemonSocketInterface::BluetoothDaemonSocketInterface( BluetoothDaemonSocketModule* aModule)
--- a/dom/bluetooth/bluedroid/BluetoothOppManager.cpp +++ b/dom/bluetooth/bluedroid/BluetoothOppManager.cpp @@ -167,28 +167,29 @@ public: return NS_OK; }; private: nsCOMPtr<nsIInputStream> mInputStream; uint32_t mAvailablePacketSize; }; -class BluetoothOppManager::CloseSocketTask final : public Task +class BluetoothOppManager::CloseSocketTask final : public Runnable { public: CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket) { MOZ_ASSERT(aSocket); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); mSocket->Close(); + return NS_OK; } private: RefPtr<BluetoothSocket> mSocket; }; BluetoothOppManager::BluetoothOppManager() : mConnected(false) , mRemoteObexVersion(0) @@ -1153,18 +1154,18 @@ BluetoothOppManager::ClientDataHandler(U SendDisconnectRequest(); FileTransferComplete(); } else if (mLastCommand == ObexRequestCode::Disconnect) { AfterOppDisconnected(); // Most devices will directly terminate connection after receiving // Disconnect request, so we make a delay here. If the socket hasn't been // disconnected, we will close it. if (mSocket) { - MessageLoop::current()-> - PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000); + MessageLoop::current()->PostDelayedTask( + MakeAndAddRef<CloseSocketTask>(mSocket), 1000); } } else if (mLastCommand == ObexRequestCode::Connect) { MOZ_ASSERT(!mFileName.IsEmpty()); MOZ_ASSERT(mBlob); AfterOppConnected(); // Ensure valid access to remote information
--- a/dom/bluetooth/bluedroid/BluetoothSocket.cpp +++ b/dom/bluetooth/bluedroid/BluetoothSocket.cpp @@ -195,61 +195,66 @@ private: class SocketConnectTask final : public SocketIOTask<DroidSocketImpl> { public: SocketConnectTask(DroidSocketImpl* aDroidSocketImpl, int aFd) : SocketIOTask<DroidSocketImpl>(aDroidSocketImpl) , mFd(aFd) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!GetIO()->IsConsumerThread()); MOZ_ASSERT(!IsCanceled()); GetIO()->Connect(mFd); + + return NS_OK; } private: int mFd; }; class SocketListenTask final : public SocketIOTask<DroidSocketImpl> { public: SocketListenTask(DroidSocketImpl* aDroidSocketImpl, int aFd) : SocketIOTask<DroidSocketImpl>(aDroidSocketImpl) , mFd(aFd) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!GetIO()->IsConsumerThread()); if (!IsCanceled()) { GetIO()->Listen(mFd); } + return NS_OK; } private: int mFd; }; class SocketConnectClientFdTask final : public SocketIOTask<DroidSocketImpl> { SocketConnectClientFdTask(DroidSocketImpl* aImpl) : SocketIOTask<DroidSocketImpl>(aImpl) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!GetIO()->IsConsumerThread()); GetIO()->ConnectClientFd(); + + return NS_OK; } }; void DroidSocketImpl::Connect(int aFd) { MOZ_ASSERT(aFd >= 0); @@ -298,17 +303,17 @@ DroidSocketImpl::Accept(int aFd) int res = TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, flags | O_NONBLOCK)); NS_ENSURE_TRUE_VOID(!res); } SetFd(aFd); mConnectionStatus = SOCKET_IS_CONNECTED; GetConsumerThread()->PostTask( - FROM_HERE, new SocketEventTask(this, SocketEventTask::CONNECT_SUCCESS)); + MakeAndAddRef<SocketEventTask>(this, SocketEventTask::CONNECT_SUCCESS)); AddWatchers(READ_WATCHER, true); if (HasPendingData()) { AddWatchers(WRITE_WATCHER, false); } } void @@ -342,22 +347,24 @@ DroidSocketImpl::OnSocketCanReceiveWitho class AcceptTask final : public SocketIOTask<DroidSocketImpl> { public: AcceptTask(DroidSocketImpl* aDroidSocketImpl, int aFd) : SocketIOTask<DroidSocketImpl>(aDroidSocketImpl) , mFd(aFd) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!GetIO()->IsConsumerThread()); MOZ_ASSERT(!IsCanceled()); GetIO()->Accept(mFd); + + return NS_OK; } private: int mFd; }; class AcceptResultHandler final : public BluetoothSocketResultHandler { @@ -381,18 +388,18 @@ public: } if (aConnectionStatus != 0) { mImpl->mConsumer->NotifyError(); return; } mImpl->mConsumer->SetAddress(aBdAddress); - mImpl->GetIOLoop()->PostTask(FROM_HERE, - new AcceptTask(mImpl, fd.forget())); + mImpl->GetIOLoop()->PostTask( + mozilla::MakeAndAddRef<AcceptTask>(mImpl, fd.forget())); } void OnError(BluetoothStatus aStatus) override { MOZ_ASSERT(mImpl->IsConsumerThread()); BT_LOGR("BluetoothSocketInterface::Accept failed: %d", (int)aStatus); if (!mImpl->IsShutdownOnConsumerThread()) { @@ -411,21 +418,23 @@ private: class InvokeAcceptTask final : public SocketTask<DroidSocketImpl> { public: InvokeAcceptTask(DroidSocketImpl* aImpl, int aListenFd) : SocketTask<DroidSocketImpl>(aImpl) , mListenFd(aListenFd) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(GetIO()->IsConsumerThread()); GetIO()->mConsumer->Accept(mListenFd, new AcceptResultHandler(GetIO())); + + return NS_OK; } private: int mListenFd; }; void DroidSocketImpl::OnSocketCanAcceptWithoutBlocking(int aFd) @@ -433,17 +442,17 @@ DroidSocketImpl::OnSocketCanAcceptWithou MOZ_ASSERT(!IsConsumerThread()); MOZ_ASSERT(!mShuttingDownOnIOThread); /* When a listening socket is ready for receiving data, * we can call |Accept| on it. */ RemoveWatchers(READ_WATCHER); - GetConsumerThread()->PostTask(FROM_HERE, new InvokeAcceptTask(this, aFd)); + GetConsumerThread()->PostTask(MakeAndAddRef<InvokeAcceptTask>(this, aFd)); } void DroidSocketImpl::OnFileCanWriteWithoutBlocking(int aFd) { if (mConnectionStatus == SOCKET_IS_CONNECTED) { OnSocketCanSendWithoutBlocking(aFd); } else if (mConnectionStatus == SOCKET_IS_CONNECTING) { @@ -478,17 +487,17 @@ DroidSocketImpl::OnSocketCanConnectWitho /* We follow Posix behaviour here: Connect operations are * complete once we can write to the connecting socket. */ mConnectionStatus = SOCKET_IS_CONNECTED; GetConsumerThread()->PostTask( - FROM_HERE, new SocketEventTask(this, SocketEventTask::CONNECT_SUCCESS)); + MakeAndAddRef<SocketEventTask>(this, SocketEventTask::CONNECT_SUCCESS)); AddWatchers(READ_WATCHER, true); if (HasPendingData()) { AddWatchers(WRITE_WATCHER, false); } } // |DataSocketIO| @@ -514,43 +523,45 @@ DroidSocketImpl::QueryReceiveBuffer( class DroidSocketImpl::ReceiveTask final : public SocketTask<DroidSocketImpl> { public: ReceiveTask(DroidSocketImpl* aIO, UnixSocketBuffer* aBuffer) : SocketTask<DroidSocketImpl>(aIO) , mBuffer(aBuffer) { } - void Run() override + NS_IMETHOD Run() override { DroidSocketImpl* io = SocketTask<DroidSocketImpl>::GetIO(); MOZ_ASSERT(io->IsConsumerThread()); if (NS_WARN_IF(io->IsShutdownOnConsumerThread())) { // Since we've already explicitly closed and the close // happened before this, this isn't really an error. - return; + return NS_OK; } BluetoothSocket* bluetoothSocket = io->GetBluetoothSocket(); MOZ_ASSERT(bluetoothSocket); bluetoothSocket->ReceiveSocketData(mBuffer); + + return NS_OK; } private: UniquePtr<UnixSocketBuffer> mBuffer; }; void DroidSocketImpl::ConsumeBuffer() { - GetConsumerThread()->PostTask(FROM_HERE, - new ReceiveTask(this, mBuffer.release())); + GetConsumerThread()->PostTask( + MakeAndAddRef<ReceiveTask>(this, mBuffer.release())); } void DroidSocketImpl::DiscardBuffer() { // Nothing to do. } @@ -600,18 +611,18 @@ public: } if (aConnectionStatus != 0) { mImpl->mConsumer->NotifyError(); return; } mImpl->mConsumer->SetAddress(aBdAddress); - mImpl->GetIOLoop()->PostTask(FROM_HERE, - new SocketConnectTask(mImpl, aFd)); + mImpl->GetIOLoop()->PostTask( + mozilla::MakeAndAddRef<SocketConnectTask>(mImpl, aFd)); } void OnError(BluetoothStatus aStatus) override { MOZ_ASSERT(mImpl->IsConsumerThread()); BT_WARNING("Connect failed: %d", (int)aStatus); if (!mImpl->IsShutdownOnConsumerThread()) { @@ -677,17 +688,18 @@ public: { MOZ_ASSERT(mImpl); } void Listen(int aFd) override { MOZ_ASSERT(mImpl->IsConsumerThread()); - mImpl->GetIOLoop()->PostTask(FROM_HERE, new SocketListenTask(mImpl, aFd)); + mImpl->GetIOLoop()->PostTask( + mozilla::MakeAndAddRef<SocketListenTask>(mImpl, aFd)); } void OnError(BluetoothStatus aStatus) override { MOZ_ASSERT(mImpl->IsConsumerThread()); BT_WARNING("Listen failed: %d", (int)aStatus); } @@ -771,18 +783,18 @@ BluetoothSocket::ReceiveSocketData(Uniqu void BluetoothSocket::SendSocketData(UnixSocketIOBuffer* aBuffer) { MOZ_ASSERT(mImpl); MOZ_ASSERT(mImpl->IsConsumerThread()); MOZ_ASSERT(!mImpl->IsShutdownOnConsumerThread()); mImpl->GetIOLoop()->PostTask( - FROM_HERE, - new SocketIOSendTask<DroidSocketImpl, UnixSocketIOBuffer>(mImpl, aBuffer)); + MakeAndAddRef<SocketIOSendTask<DroidSocketImpl, UnixSocketIOBuffer>>( + mImpl, aBuffer)); } // |SocketBase| void BluetoothSocket::Close() { if (!mImpl) { @@ -855,16 +867,16 @@ BluetoothSocket::Cleanup() if (mCurrentRes) { mSocketInterface->Close(mCurrentRes); } // From this point on, we consider mImpl as being deleted. We // sever the relationship here so any future calls to listen // or connect will create a new implementation. mImpl->ShutdownOnConsumerThread(); - mImpl->GetIOLoop()->PostTask(FROM_HERE, new SocketIOShutdownTask(mImpl)); + mImpl->GetIOLoop()->PostTask(MakeAndAddRef<SocketIOShutdownTask>(mImpl)); mImpl = nullptr; mSocketInterface = nullptr; mObserver = nullptr; mCurrentRes = nullptr; mDeviceAddress.Clear(); }
--- a/dom/bluetooth/bluedroid/BluetoothSocketMessageWatcher.cpp +++ b/dom/bluetooth/bluedroid/BluetoothSocketMessageWatcher.cpp @@ -272,41 +272,43 @@ SocketMessageWatcher::ReadBdAddress(unsi SocketMessageWatcherTask::SocketMessageWatcherTask( SocketMessageWatcher* aWatcher) : mWatcher(aWatcher) { MOZ_ASSERT(mWatcher); } -void +NS_IMETHODIMP SocketMessageWatcherTask::Run() { mWatcher->Watch(); + return NS_OK; } // // DeleteSocketMessageWatcherTask // DeleteSocketMessageWatcherTask::DeleteSocketMessageWatcherTask( BluetoothSocketResultHandler* aRes) : mRes(aRes) { MOZ_ASSERT(mRes); } -void +NS_IMETHODIMP DeleteSocketMessageWatcherTask::Run() { // look up hash table for the watcher corresponding to |mRes| SocketMessageWatcherWrapper* wrapper = sWatcherHashtable.Get(mRes); if (!wrapper) { - return; + return NS_OK; } // stop the watcher if it exists SocketMessageWatcher* watcher = wrapper->GetSocketMessageWatcher(); watcher->StopWatching(); watcher->Proceed(STATUS_DONE); + return NS_OK; } END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/bluedroid/BluetoothSocketMessageWatcher.h +++ b/dom/bluetooth/bluedroid/BluetoothSocketMessageWatcher.h @@ -79,36 +79,36 @@ private: unsigned char mLen; uint8_t mBuf[MSG1_SIZE + MSG2_SIZE]; RefPtr<BluetoothSocketResultHandler> mRes; }; /* |SocketMessageWatcherTask| starts a SocketMessageWatcher * on the I/O task */ -class SocketMessageWatcherTask final : public Task +class SocketMessageWatcherTask final : public Runnable { public: SocketMessageWatcherTask(SocketMessageWatcher* aWatcher); - void Run() override; + NS_IMETHOD Run() override; private: SocketMessageWatcher* mWatcher; }; /* |DeleteSocketMessageWatcherTask| deletes a watching SocketMessageWatcher * on the I/O task */ -class DeleteSocketMessageWatcherTask final : public Task +class DeleteSocketMessageWatcherTask final : public Runnable { public: DeleteSocketMessageWatcherTask(BluetoothSocketResultHandler* aRes); - void Run() override; + NS_IMETHOD Run() override; private: BluetoothSocketResultHandler* mRes; }; END_BLUETOOTH_NAMESPACE #endif // mozilla_dom_bluetooth_bluedroid_BluetoothSocketMessageWatcher_h
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp +++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp @@ -103,51 +103,53 @@ public: BT_WARNING("Unable to get value for '" AUDIO_VOLUME_BT_SCO_ID "'"); return NS_OK; } protected: ~GetVolumeTask() { } }; -class BluetoothHfpManager::CloseScoTask : public Task +class BluetoothHfpManager::CloseScoTask : public Runnable { private: - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(sBluetoothHfpManager); sBluetoothHfpManager->DisconnectSco(); + return NS_OK; } }; class BluetoothHfpManager::CloseScoRunnable : public Runnable { public: NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); MessageLoop::current()->PostDelayedTask( - FROM_HERE, new CloseScoTask(), sBusyToneInterval); + MakeAndAddRef<CloseScoTask>(), sBusyToneInterval); return NS_OK; } }; -class BluetoothHfpManager::RespondToBLDNTask : public Task +class BluetoothHfpManager::RespondToBLDNTask : public Runnable { private: - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(sBluetoothHfpManager); if (!sBluetoothHfpManager->mDialingRequestProcessed) { sBluetoothHfpManager->mDialingRequestProcessed = true; sBluetoothHfpManager->SendResponse(HFP_AT_RESPONSE_ERROR); } + return NS_OK; } }; NS_IMPL_ISUPPORTS(BluetoothHfpManager::GetVolumeTask, nsISettingsServiceCallback); /** * Call @@ -1592,28 +1594,26 @@ void BluetoothHfpManager::DialCallNotifi // We need to respond OK/Error for dial requests for every case listed above, // 1) and 2): Respond in either RespondToBLDNTask or // HandleCallStateChanged() // 3): Respond here if (message.IsEmpty()) { mDialingRequestProcessed = false; NotifyDialer(NS_LITERAL_STRING("BLDN")); - MessageLoop::current()->PostDelayedTask(FROM_HERE, - new RespondToBLDNTask(), + MessageLoop::current()->PostDelayedTask(MakeAndAddRef<RespondToBLDNTask>(), sWaitingForDialingInterval); } else if (message[0] == '>') { mDialingRequestProcessed = false; nsAutoCString newMsg("ATD"); newMsg += StringHead(message, message.Length() - 1); NotifyDialer(NS_ConvertUTF8toUTF16(newMsg)); - MessageLoop::current()->PostDelayedTask(FROM_HERE, - new RespondToBLDNTask(), + MessageLoop::current()->PostDelayedTask(MakeAndAddRef<RespondToBLDNTask>(), sWaitingForDialingInterval); } else { SendResponse(HFP_AT_RESPONSE_OK); nsAutoCString newMsg("ATD"); newMsg += StringHead(message, message.Length() - 1); NotifyDialer(NS_ConvertUTF8toUTF16(newMsg)); } @@ -1761,15 +1761,14 @@ BluetoothHfpManager::KeyPressedNotificat */ NotifyDialer(NS_LITERAL_STRING("CHUP")); } } else { // BLDN NotifyDialer(NS_LITERAL_STRING("BLDN")); - MessageLoop::current()->PostDelayedTask(FROM_HERE, - new RespondToBLDNTask(), + MessageLoop::current()->PostDelayedTask(MakeAndAddRef<RespondToBLDNTask>(), sWaitingForDialingInterval); } } NS_IMPL_ISUPPORTS(BluetoothHfpManager, nsIObserver)
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp +++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp @@ -379,19 +379,19 @@ static StaticAutoPtr<Monitor> sStopBluet // Protects against bug 969447. static StaticAutoPtr<Monitor> sGetPropertyMonitor; typedef void (*UnpackFunc)(DBusMessage*, DBusError*, BluetoothValue&, nsAString&); typedef bool (*FilterFunc)(const BluetoothValue&); static void -DispatchToDBusThread(Task* task) +DispatchToDBusThread(already_AddRefed<Runnable> task) { - XRE_GetIOMessageLoop()->PostTask(FROM_HERE, task); + XRE_GetIOMessageLoop()->PostTask(Move(task)); } static nsresult DispatchToBtThread(nsIRunnable* aRunnable) { /* Due to the fact that the startup and shutdown of the Bluetooth * system can take an indefinite amount of time, a separate thread * is used for running blocking calls. The thread is not intended @@ -623,43 +623,44 @@ public: hid->HandleInputPropertyChanged(mSignal); return NS_OK; } private: BluetoothSignal mSignal; }; -class TryFiringAdapterAddedTask : public Task +class TryFiringAdapterAddedTask : public Runnable { public: - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); + return NS_OK; } }; class TryFiringAdapterAddedRunnable : public Runnable { public: TryFiringAdapterAddedRunnable(bool aDelay) : mDelay(aDelay) { } nsresult Run() { MOZ_ASSERT(NS_IsMainThread()); if (mDelay) { MessageLoop::current()-> - PostDelayedTask(FROM_HERE, new TryFiringAdapterAddedTask(), + PostDelayedTask(MakeAndAddRef<TryFiringAdapterAddedTask>(), sWaitingForAdapterNameInterval); } else { MessageLoop::current()-> - PostTask(FROM_HERE, new TryFiringAdapterAddedTask()); + PostTask(MakeAndAddRef<TryFiringAdapterAddedTask>()); } return NS_OK; } private: bool mDelay; }; @@ -1251,44 +1252,44 @@ AppendDeviceName(BluetoothSignal& aSigna BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(devicePath).get(), DBUS_DEVICE_IFACE, "GetProperties", DBUS_TYPE_INVALID); NS_ENSURE_TRUE_VOID(success); Unused << handler.forget(); // picked up by callback handler } -class SetPairingConfirmationTask : public Task +class SetPairingConfirmationTask : public Runnable { public: SetPairingConfirmationTask(const BluetoothAddress& aDeviceAddress, bool aConfirm, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mConfirm(aConfirm) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); nsAutoString errorStr; BluetoothValue v = true; DBusMessage *msg = nullptr; if (!sPairingReqTable->Get(mDeviceAddress, &msg) && mRunnable) { BT_WARNING("%s: Couldn't get original request message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't get original request message."); DispatchBluetoothReply(mRunnable, v, errorStr); - return; + return NS_OK; } DBusMessage *reply; if (mConfirm) { reply = dbus_message_new_method_return(msg); } else { reply = dbus_message_new_error(msg, "org.bluez.Error.Rejected", @@ -1297,30 +1298,31 @@ public: if (!reply) { BT_WARNING("%s: Memory can't be allocated for the message.", __FUNCTION__); dbus_message_unref(msg); errorStr.AssignLiteral("Memory can't be allocated for the message."); if (mRunnable) { DispatchBluetoothReply(mRunnable, v, errorStr); } - return; + return NS_OK; } bool result = sDBusConnection->Send(reply); if (!result) { errorStr.AssignLiteral("Can't send message!"); } dbus_message_unref(msg); dbus_message_unref(reply); sPairingReqTable->Remove(mDeviceAddress); if (mRunnable) { DispatchBluetoothReply(mRunnable, v, errorStr); } + return NS_OK; } private: BluetoothAddress mDeviceAddress; bool mConfirm; RefPtr<BluetoothReplyRunnable> mRunnable; }; @@ -1504,18 +1506,18 @@ AgentEventFilter(DBusConnection *conn, D errorStr.AssignLiteral("Invalid arguments: RequestPairingConsent()"); goto handle_error; } BluetoothAddress address; GetAddressFromObjectPath(NS_ConvertUTF8toUTF16(objectPath), address); sPairingReqTable->Put(address, msg); - Task* task = new SetPairingConfirmationTask(address, true, nullptr); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<SetPairingConfirmationTask>(address, true, nullptr)); // Increase dbus message reference counts, it will be decreased in // SetPairingConfirmationTask dbus_message_ref(msg); // Do not send a notification to upper layer return DBUS_HANDLER_RESULT_HANDLED; } else { #ifdef DEBUG BT_WARNING("agent handler %s: Unhandled event. Ignore.", __FUNCTION__); @@ -1684,23 +1686,23 @@ private: NS_ENSURE_TRUE(success, false); Unused << handler.forget(); // picked up by callback handler return true; } }; -class AddReservedServiceRecordsTask : public Task +class AddReservedServiceRecordsTask : public Runnable { public: AddReservedServiceRecordsTask() { } - void Run() + NS_IMETHOD Run() override { static const dbus_uint32_t sServices[] = { BluetoothServiceClass::HANDSFREE_AG, BluetoothServiceClass::HEADSET_AG, BluetoothServiceClass::OBJECT_PUSH }; MOZ_ASSERT(!NS_IsMainThread()); // I/O thread @@ -1715,34 +1717,35 @@ public: bool success = sDBusConnection->SendWithReply( DBusReplyHandler::Callback, handler.get(), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "AddReservedServiceRecords", DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &services, ArrayLength(sServices), DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << handler.forget(); /* picked up by callback handler */ + + return NS_OK; } }; class PrepareAdapterRunnable : public Runnable { public: PrepareAdapterRunnable() { } NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); - Task* task = new AddReservedServiceRecordsTask(); - DispatchToDBusThread(task); + DispatchToDBusThread(MakeAndAddRef<AddReservedServiceRecordsTask>()); return NS_OK; } }; class RequestPlayStatusTask : public Runnable { public: @@ -2066,62 +2069,62 @@ BluetoothDBusService::IsReady() { if (!IsEnabled() || !sDBusConnection || IsToggling()) { BT_WARNING("Bluetooth service is not ready yet!"); return false; } return true; } -class StartDBusConnectionTask : public Task +class StartDBusConnectionTask : public Runnable { public: StartDBusConnectionTask(RawDBusConnection* aConnection) : mConnection(aConnection) { MOZ_ASSERT(mConnection); } - void Run() + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (sDBusConnection) { BT_WARNING("DBus connection has already been established."); RefPtr<Runnable> runnable = new BluetoothService::ToggleBtAck(true); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } - return; + return NS_OK; } // Add a filter for all incoming messages_base if (!dbus_connection_add_filter(mConnection->GetConnection(), EventFilter, nullptr, nullptr)) { BT_WARNING("Cannot create DBus Event Filter for DBus Thread!"); RefPtr<Runnable> runnable = new BluetoothService::ToggleBtAck(false); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } - return; + return NS_OK; } mConnection->Watch(); if (!sPairingReqTable) { sPairingReqTable = new nsDataHashtable<BluetoothAddressHashKey, DBusMessage* >; } sDBusConnection = mConnection.release(); RefPtr<Runnable> runnable = new BluetoothService::ToggleBtAck(true); - if (NS_FAILED(NS_DispatchToMainThread(runnable))) { + if (NS_FAILED(NS_DispatchToMainThread(runnable.forget()))) { BT_WARNING("Failed to dispatch to main thread!"); - return; + return NS_OK; } /* Normally we'll receive the signal 'AdapterAdded' with the adapter object * path from the DBus daemon during start up. So, there's no need to query * the object path of default adapter here. However, if we restart from a * crash, the default adapter might already be available, so we ask the * daemon explicitly here. */ @@ -2131,16 +2134,17 @@ public: BLUEZ_DBUS_BASE_IFC, "/", DBUS_MANAGER_IFACE, "DefaultAdapter", DBUS_TYPE_INVALID); if (!success) { BT_WARNING("Failed to query default adapter!"); } } + return NS_OK; } private: UniquePtr<RawDBusConnection> mConnection; }; class StartBluetoothRunnable final : public Runnable { @@ -2185,18 +2189,17 @@ public: dbus_bus_add_match(connection->GetConnection(), sBluetoothDBusSignals[i], &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); } } - Task* task = new StartDBusConnectionTask(connection); - DispatchToDBusThread(task); + DispatchToDBusThread(MakeAndAddRef<StartDBusConnectionTask>(connection)); return NS_OK; } }; nsresult BluetoothDBusService::StartInternal(BluetoothReplyRunnable* aRunnable) { @@ -2235,33 +2238,33 @@ public: nsresult rv = NS_DispatchToMainThread(runnable); if (NS_FAILED(rv)) { BT_WARNING("Failed to dispatch to main thread!"); } return rv; } }; -class DeleteDBusConnectionTask final : public Task +class DeleteDBusConnectionTask final : public Runnable { public: DeleteDBusConnectionTask() { } - void Run() + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread if (!sDBusConnection) { BT_WARNING("DBus connection has not been established."); RefPtr<Runnable> runnable = new BluetoothService::ToggleBtAck(false); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to main thread!"); } - return; + return NS_OK; } for (uint32_t i = 0; i < ArrayLength(sBluetoothDBusSignals); ++i) { dbus_bus_remove_match(sDBusConnection->GetConnection(), sBluetoothDBusSignals[i], NULL); } dbus_connection_remove_filter(sDBusConnection->GetConnection(), @@ -2294,33 +2297,34 @@ public: // We can only dispatch to the BT thread if we're on the main // thread. Thus we dispatch our runnable to the main thread // from where it will forward itself to the BT thread. RefPtr<Runnable> runnable = new DisableBluetoothRunnable(); if (NS_FAILED(NS_DispatchToMainThread(runnable))) { BT_WARNING("Failed to dispatch to BT thread!"); } + return NS_OK; } }; class StopBluetoothRunnable final : public Runnable { public: NS_IMETHOD Run() { MOZ_ASSERT(!NS_IsMainThread()); // BT thread // This could block. It should never be run on the main thread. MonitorAutoLock lock(*sStopBluetoothMonitor); if (sConnectedDeviceCount > 0) { lock.Wait(PR_SecondsToInterval(TIMEOUT_FORCE_TO_DISABLE_BT)); } - DispatchToDBusThread(new DeleteDBusConnectionTask()); + DispatchToDBusThread(MakeAndAddRef<DeleteDBusConnectionTask>()); return NS_OK; } }; nsresult BluetoothDBusService::StopInternal(BluetoothReplyRunnable* aRunnable) { @@ -2438,41 +2442,43 @@ protected: return true; } private: RefPtr<BluetoothReplyRunnable> mRunnable; nsString mAdapterPath; }; -class DefaultAdapterTask : public Task +class DefaultAdapterTask : public Runnable { public: DefaultAdapterTask(BluetoothReplyRunnable* aRunnable) : mRunnable(aRunnable) { MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); RefPtr<DefaultAdapterPathReplyHandler> handler = new DefaultAdapterPathReplyHandler(mRunnable); bool success = sDBusConnection->SendWithReply( DefaultAdapterPathReplyHandler::Callback, handler.get(), 1000, BLUEZ_DBUS_BASE_IFC, "/", DBUS_MANAGER_IFACE, "DefaultAdapter", DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << handler.forget(); // picked up by callback handler + + return NS_OK; } private: RefPtr<BluetoothReplyRunnable> mRunnable; }; nsresult BluetoothDBusService::GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) @@ -2484,18 +2490,17 @@ BluetoothDBusService::GetAdaptersInterna */ if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } - Task* task = new DefaultAdapterTask(aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread(MakeAndAddRef<DefaultAdapterTask>(aRunnable)); return NS_OK; } static void OnSendDiscoveryMessageReply(DBusMessage *aReply, void *aData) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread @@ -2508,44 +2513,46 @@ OnSendDiscoveryMessageReply(DBusMessage RefPtr<BluetoothReplyRunnable> runnable = dont_AddRef<BluetoothReplyRunnable>( static_cast<BluetoothReplyRunnable*>(aData)); DispatchBluetoothReply(runnable.get(), BluetoothValue(true), errorStr); } -class SendDiscoveryMessageTask : public Task +class SendDiscoveryMessageTask : public Runnable { public: SendDiscoveryMessageTask(const char* aMessageName, BluetoothReplyRunnable* aRunnable) : mMessageName(aMessageName) , mRunnable(aRunnable) { MOZ_ASSERT(!mMessageName.IsEmpty()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); bool success = sDBusConnection->SendWithReply( OnSendDiscoveryMessageReply, static_cast<void*>(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, mMessageName.get(), DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << mRunnable.forget(); // picked up by callback handler + + return NS_OK; } private: const nsCString mMessageName; RefPtr<BluetoothReplyRunnable> mRunnable; }; nsresult @@ -2557,18 +2564,18 @@ BluetoothDBusService::SendDiscoveryMessa if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } - Task* task = new SendDiscoveryMessageTask(aMessageName, aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<SendDiscoveryMessageTask>(aMessageName, aRunnable)); return NS_OK; } nsresult BluetoothDBusService::SendInputMessage(const nsAString& aDeviceAddress, const nsAString& aMessage) { @@ -2582,17 +2589,17 @@ BluetoothDBusService::SendInputMessage(c return NS_ERROR_FAILURE; } MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsString objectPath = GetObjectPathFromAddress(sAdapterPath, aDeviceAddress); return SendAsyncDBusMessage(objectPath, DBUS_INPUT_IFACE, aMessage, callback); } -class SendAsyncDBusMessageTask : public Task +class SendAsyncDBusMessageTask : public Runnable { public: SendAsyncDBusMessageTask(DBusReplyCallback aCallback, BluetoothServiceClass aServiceClass, const nsACString& aObjectPath, const char* aInterface, const nsACString& aMessage) : mCallback(aCallback) @@ -2601,28 +2608,30 @@ public: , mInterface(aInterface) , mMessage(aMessage) { MOZ_ASSERT(!mObjectPath.IsEmpty()); MOZ_ASSERT(!mInterface.IsEmpty()); MOZ_ASSERT(!mMessage.IsEmpty()); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); static_assert(sizeof(BluetoothServiceClass) <= sizeof(intptr_t), "BluetoothServiceClass cannot be passed via intptr_t"); bool success = sDBusConnection->SendWithReply( mCallback, NS_INT32_TO_PTR(mServiceClass), -1, BLUEZ_DBUS_BASE_IFC, mObjectPath.get(), mInterface.get(), mMessage.get(), DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); + + return NS_OK; } private: DBusReplyCallback mCallback; BluetoothServiceClass mServiceClass; const nsCString mObjectPath; const nsCString mInterface; const nsCString mMessage; @@ -2645,22 +2654,22 @@ BluetoothDBusService::SendAsyncDBusMessa serviceClass = BluetoothServiceClass::A2DP; } else if (!strcmp(aInterface, DBUS_INPUT_IFACE)) { serviceClass = BluetoothServiceClass::HID; } else { MOZ_ASSERT(false); return NS_ERROR_FAILURE; } - Task* task = new SendAsyncDBusMessageTask(aCallback, + DispatchToDBusThread( + MakeAndAddRef<SendAsyncDBusMessageTask>(aCallback, serviceClass, NS_ConvertUTF16toUTF8(aObjectPath), aInterface, - NS_ConvertUTF16toUTF8(aMessage)); - DispatchToDBusThread(task); + NS_ConvertUTF16toUTF8(aMessage))); return NS_OK; } nsresult BluetoothDBusService::SendSinkMessage(const nsAString& aDeviceAddress, const nsAString& aMessage) { @@ -2819,34 +2828,36 @@ private: nsString mObjectPath; const nsTArray<BluetoothAddress> mDeviceAddresses; nsTArray<nsString>::size_type mProcessedDeviceAddresses; const FilterFunc mFilterFunc; RefPtr<BluetoothReplyRunnable> mRunnable; BluetoothValue mValues; }; -class ProcessRemainingDeviceAddressesTask : public Task +class ProcessRemainingDeviceAddressesTask : public Runnable { public: ProcessRemainingDeviceAddressesTask( BluetoothArrayOfDevicePropertiesReplyHandler* aHandler, BluetoothReplyRunnable* aRunnable) : mHandler(aHandler) , mRunnable(aRunnable) { MOZ_ASSERT(mHandler); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread mHandler->ProcessRemainingDeviceAddresses(); + + return NS_OK; } private: RefPtr<BluetoothArrayOfDevicePropertiesReplyHandler> mHandler; RefPtr<BluetoothReplyRunnable> mRunnable; }; nsresult @@ -2878,18 +2889,18 @@ BluetoothDBusService::GetConnectedDevice profile->GetAddress(address); deviceAddresses.AppendElement(address); } BluetoothArrayOfDevicePropertiesReplyHandler* handler = new BluetoothArrayOfDevicePropertiesReplyHandler(deviceAddresses, GetConnectedDevicesFilter, aRunnable); - Task* task = new ProcessRemainingDeviceAddressesTask(handler, aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<ProcessRemainingDeviceAddressesTask>(handler, aRunnable)); return NS_OK; } nsresult BluetoothDBusService::GetPairedDevicePropertiesInternal( const nsTArray<BluetoothAddress>& aDeviceAddresses, BluetoothReplyRunnable* aRunnable) @@ -2901,30 +2912,30 @@ BluetoothDBusService::GetPairedDevicePro DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } BluetoothArrayOfDevicePropertiesReplyHandler* handler = new BluetoothArrayOfDevicePropertiesReplyHandler(aDeviceAddresses, GetPairedDevicesFilter, aRunnable); - Task* task = new ProcessRemainingDeviceAddressesTask(handler, aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<ProcessRemainingDeviceAddressesTask>(handler, aRunnable)); return NS_OK; } nsresult BluetoothDBusService::FetchUuidsInternal(const BluetoothAddress& aDeviceAddress, BluetoothReplyRunnable* aRunnable) { return NS_OK; } -class SetPropertyTask : public Task +class SetPropertyTask : public Runnable { public: SetPropertyTask(BluetoothObjectType aType, const nsACString& aName, BluetoothReplyRunnable* aRunnable) : mType(aType) , mName(aName) , mRunnable(aRunnable) @@ -2989,19 +3000,21 @@ public: SetUInt32PropertyTask(BluetoothObjectType aType, const nsACString& aName, uint32_t aValue, BluetoothReplyRunnable* aRunnable) : SetPropertyTask(aType, aName, aRunnable) , mValue(aValue) { } - void Run() override + NS_IMETHOD Run() override { Send(DBUS_TYPE_UINT32, &mValue); + + return NS_OK; } private: dbus_uint32_t mValue; }; class SetStringPropertyTask : public SetPropertyTask { @@ -3009,20 +3022,22 @@ public: SetStringPropertyTask(BluetoothObjectType aType, const nsACString& aName, const nsACString& aValue, BluetoothReplyRunnable* aRunnable) : SetPropertyTask(aType, aName, aRunnable) , mValue(aValue) { } - void Run() override + NS_IMETHOD Run() override { const char* value = mValue.get(); Send(DBUS_TYPE_STRING, &value); + + return NS_OK; } private: const nsCString mValue; }; class SetBooleanPropertyTask : public SetPropertyTask { @@ -3031,19 +3046,21 @@ public: const nsACString& aName, dbus_bool_t aValue, BluetoothReplyRunnable* aRunnable) : SetPropertyTask(aType, aName, aRunnable) , mValue(aValue) { } - void Run() override + NS_IMETHOD Run() override { Send(DBUS_TYPE_BOOLEAN, &mValue); + + return NS_OK; } private: dbus_bool_t mValue; }; nsresult BluetoothDBusService::SetProperty(BluetoothObjectType aType, @@ -3053,54 +3070,54 @@ BluetoothDBusService::SetProperty(Blueto MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } - Task* task; + RefPtr<Runnable> task; if (aValue.value().type() == BluetoothValue::Tuint32_t) { - task = new SetUInt32PropertyTask(aType, + task = MakeAndAddRef<SetUInt32PropertyTask>(aType, NS_ConvertUTF16toUTF8(aValue.name()), aValue.value().get_uint32_t(), aRunnable); } else if (aValue.value().type() == BluetoothValue::TnsString) { - task = new SetStringPropertyTask(aType, + task = MakeAndAddRef<SetStringPropertyTask>(aType, NS_ConvertUTF16toUTF8(aValue.name()), NS_ConvertUTF16toUTF8(aValue.value().get_nsString()), aRunnable); } else if (aValue.value().type() == BluetoothValue::Tbool) { - task = new SetBooleanPropertyTask(aType, + task = MakeAndAddRef<SetBooleanPropertyTask>(aType, NS_ConvertUTF16toUTF8(aValue.name()), aValue.value().get_bool(), aRunnable); } else { BT_WARNING("Property type not handled!"); return NS_ERROR_FAILURE; } - DispatchToDBusThread(task); + DispatchToDBusThread(task.forget()); return NS_OK; } -class CreatePairedDeviceInternalTask : public Task +class CreatePairedDeviceInternalTask : public Runnable { public: CreatePairedDeviceInternalTask(const BluetoothAddress& aDeviceAddress, int aTimeout, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mTimeout(aTimeout) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsString deviceAddressStr; AddressToString(mDeviceAddress, deviceAddressStr); auto utf8DeviceAddressStr = NS_ConvertUTF16toUTF8(deviceAddressStr); @@ -3116,68 +3133,70 @@ public: BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "CreatePairedDevice", DBUS_TYPE_STRING, &deviceAddress, DBUS_TYPE_OBJECT_PATH, &deviceAgentPath, DBUS_TYPE_STRING, &capabilities, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << mRunnable.forget(); // picked up by callback handler /** * FIXME: Bug 820274 * * If the user turns off Bluetooth in the middle of pairing process, * the callback function GetObjectPathCallback may still be called * while enabling next time by dbus daemon. To prevent this from * happening, added a flag to distinguish if Bluetooth has been * turned off. Nevertheless, we need a check if there is a better * solution. * * Please see Bug 818696 for more information. */ sIsPairing++; + + return NS_OK; } private: BluetoothAddress mDeviceAddress; int mTimeout; RefPtr<BluetoothReplyRunnable> mRunnable; }; nsresult BluetoothDBusService::CreatePairedDeviceInternal( const BluetoothAddress& aDeviceAddress, int aTimeout, BluetoothReplyRunnable* aRunnable) { - Task* task = new CreatePairedDeviceInternalTask(aDeviceAddress, + DispatchToDBusThread( + MakeAndAddRef<CreatePairedDeviceInternalTask>(aDeviceAddress, aTimeout, - aRunnable); - DispatchToDBusThread(task); + aRunnable)); return NS_OK; } -class RemoveDeviceTask : public Task +class RemoveDeviceTask : public Runnable { public: RemoveDeviceTask(const BluetoothAddress& aDeviceAddress, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); nsCString deviceObjectPath = NS_ConvertUTF16toUTF8(GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); @@ -3185,19 +3204,21 @@ public: bool success = sDBusConnection->SendWithReply( OnRemoveDeviceReply, static_cast<void*>(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(sAdapterPath).get(), DBUS_ADAPTER_IFACE, "RemoveDevice", DBUS_TYPE_OBJECT_PATH, &cstrDeviceObjectPath, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << mRunnable.forget(); // picked up by callback handler + + return NS_OK; } protected: static void OnRemoveDeviceReply(DBusMessage* aReply, void* aData) { nsAutoString errorStr; if (!aReply) { @@ -3224,68 +3245,68 @@ BluetoothDBusService::RemoveDeviceIntern MOZ_ASSERT(NS_IsMainThread()); if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr); return NS_OK; } - Task* task = new RemoveDeviceTask(aDeviceAddress, aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<RemoveDeviceTask>(aDeviceAddress, aRunnable)); return NS_OK; } -class SetPinCodeTask : public Task +class SetPinCodeTask : public Runnable { public: SetPinCodeTask(const BluetoothAddress& aDeviceAddress, const BluetoothPinCode& aPinCode, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mPinCode(aPinCode) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread nsAutoString errorStr; BluetoothValue v = true; DBusMessage *msg; if (!sPairingReqTable->Get(mDeviceAddress, &msg)) { BT_WARNING("%s: Couldn't get original request message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't get original request message."); DispatchBluetoothReply(mRunnable, v, errorStr); - return; + return NS_OK; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { BT_WARNING("%s: Memory can't be allocated for the message.", __FUNCTION__); dbus_message_unref(msg); errorStr.AssignLiteral("Memory can't be allocated for the message."); DispatchBluetoothReply(mRunnable, v, errorStr); - return; + return NS_OK; } nsAutoString pinCodeStr; if (NS_FAILED(PinCodeToString(mPinCode, pinCodeStr))) { BT_WARNING("%s: Cannot convert pin code to string.", __FUNCTION__); dbus_message_unref(msg); dbus_message_unref(reply); errorStr.AssignLiteral("Cannot convert pin code to string."); DispatchBluetoothReply(mRunnable, v, errorStr); - return; + return NS_OK; } auto utf8PinCodeStr = NS_ConvertUTF16toUTF8(pinCodeStr); const char* pinCode = utf8PinCodeStr.get(); if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &pinCode, @@ -3297,16 +3318,18 @@ public: sDBusConnection->Send(reply); } dbus_message_unref(msg); dbus_message_unref(reply); sPairingReqTable->Remove(mDeviceAddress); DispatchBluetoothReply(mRunnable, v, errorStr); + + return NS_OK; } private: const BluetoothAddress mDeviceAddress; const BluetoothPinCode mPinCode; RefPtr<BluetoothReplyRunnable> mRunnable; }; @@ -3326,56 +3349,56 @@ BluetoothDBusService::SspReplyInternal( // Legacy interface used by Bluedroid only. } void BluetoothDBusService::SetPinCodeInternal(const BluetoothAddress& aDeviceAddress, const BluetoothPinCode& aPinCode, BluetoothReplyRunnable* aRunnable) { - Task* task = new SetPinCodeTask(aDeviceAddress, aPinCode, aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<SetPinCodeTask>(aDeviceAddress, aPinCode, aRunnable)); } -class SetPasskeyTask : public Task +class SetPasskeyTask : public Runnable { public: SetPasskeyTask(const BluetoothAddress& aDeviceAddress, uint32_t aPasskey, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) , mPasskey(aPasskey) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread nsAutoString errorStr; BluetoothValue v = true; DBusMessage *msg; if (!sPairingReqTable->Get(mDeviceAddress, &msg)) { BT_WARNING("%s: Couldn't get original request message.", __FUNCTION__); errorStr.AssignLiteral("Couldn't get original request message."); DispatchBluetoothReply(mRunnable, v, errorStr); - return; + return NS_OK; } DBusMessage *reply = dbus_message_new_method_return(msg); if (!reply) { BT_WARNING("%s: Memory can't be allocated for the message.", __FUNCTION__); dbus_message_unref(msg); errorStr.AssignLiteral("Memory can't be allocated for the message."); DispatchBluetoothReply(mRunnable, v, errorStr); - return; + return NS_OK; } uint32_t passkey = mPasskey; if (!dbus_message_append_args(reply, DBUS_TYPE_UINT32, &passkey, DBUS_TYPE_INVALID)) { BT_WARNING("%s: Couldn't append arguments to dbus message.", __FUNCTION__); @@ -3385,48 +3408,50 @@ public: sDBusConnection->Send(reply); } dbus_message_unref(msg); dbus_message_unref(reply); sPairingReqTable->Remove(mDeviceAddress); DispatchBluetoothReply(mRunnable, v, errorStr); + + return NS_OK; } private: BluetoothAddress mDeviceAddress; uint32_t mPasskey; RefPtr<BluetoothReplyRunnable> mRunnable; }; void BluetoothDBusService::SetPasskeyInternal( const BluetoothAddress& aDeviceAddress, uint32_t aPasskey, BluetoothReplyRunnable* aRunnable) { - Task* task = new SetPasskeyTask(aDeviceAddress, + DispatchToDBusThread( + MakeAndAddRef<SetPasskeyTask>(aDeviceAddress, aPasskey, - aRunnable); - DispatchToDBusThread(task); + aRunnable)); } void BluetoothDBusService::SetPairingConfirmationInternal( const BluetoothAddress& aDeviceAddress, bool aConfirm, BluetoothReplyRunnable* aRunnable) { MOZ_ASSERT(NS_IsMainThread()); - Task* task = new SetPairingConfirmationTask(aDeviceAddress, + DispatchToDBusThread( + MakeAndAddRef<SetPairingConfirmationTask>(aDeviceAddress, aConfirm, - aRunnable); - DispatchToDBusThread(task); + aRunnable)); } static void NextBluetoothProfileController() { MOZ_ASSERT(NS_IsMainThread()); // First, remove the task at the front which has been already done. @@ -3611,31 +3636,31 @@ public: } private: BluetoothAddress mDeviceAddress; BluetoothUuid mServiceUUID; BluetoothProfileManagerBase* mBluetoothProfileManager; }; -class GetServiceChannelTask : public Task +class GetServiceChannelTask : public Runnable { public: GetServiceChannelTask(const BluetoothAddress& aDeviceAddress, const BluetoothUuid& aServiceUUID, BluetoothProfileManagerBase* aBluetoothProfileManager) : mDeviceAddress(aDeviceAddress) , mServiceUUID(aServiceUUID) , mBluetoothProfileManager(aBluetoothProfileManager) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mBluetoothProfileManager); } - void Run() override + NS_IMETHOD Run() override { static const int sProtocolDescriptorList = 0x0004; MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsString objectPath = @@ -3654,19 +3679,21 @@ public: bool success = sDBusConnection->SendWithReply( OnGetServiceChannelReplyHandler::Callback, handler, -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(objectPath).get(), DBUS_DEVICE_IFACE, "GetServiceAttributeValue", DBUS_TYPE_STRING, &cstrServiceUUID, DBUS_TYPE_UINT16, &sProtocolDescriptorList, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << handler.forget(); // picked up by callback handler + + return NS_OK; } private: BluetoothAddress mDeviceAddress; BluetoothUuid mServiceUUID; BluetoothProfileManagerBase* mBluetoothProfileManager; }; @@ -3680,20 +3707,20 @@ BluetoothDBusService::GetServiceChannel( if (!IsReady()) { NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!"); return NS_OK; } #ifdef MOZ_WIDGET_GONK // GetServiceAttributeValue only exists in android's bluez dbus binding // implementation - Task* task = new GetServiceChannelTask(aDeviceAddress, + DispatchToDBusThread( + MakeAndAddRef<GetServiceChannelTask>(aDeviceAddress, aServiceUUID, - aManager); - DispatchToDBusThread(task); + aManager)); #else // FIXME/Bug 793977 qdot: Just set something for desktop, until we have a // parser for the GetServiceAttributes xml block // // Even though we are on the main thread already, we need to dispatch a // runnable here. OnGetServiceChannel needs mRunnable to be set, which // happens after GetServiceChannel returns. RefPtr<Runnable> r = new OnGetServiceChannelRunnable(aDeviceAddress, @@ -3701,29 +3728,29 @@ BluetoothDBusService::GetServiceChannel( 1, aManager); NS_DispatchToMainThread(r); #endif return NS_OK; } -class UpdateSdpRecordsTask : public Task +class UpdateSdpRecordsTask : public Runnable { public: UpdateSdpRecordsTask(const BluetoothAddress& aDeviceAddress, BluetoothProfileManagerBase* aBluetoothProfileManager) : mDeviceAddress(aDeviceAddress) , mBluetoothProfileManager(aBluetoothProfileManager) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mBluetoothProfileManager); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsString objectPath = GetObjectPathFromAddress(sAdapterPath, mDeviceAddress); @@ -3735,16 +3762,17 @@ public: sDBusConnection->SendWithReply(DiscoverServicesCallback, (void*)callbackRunnable, -1, BLUEZ_DBUS_BASE_IFC, NS_ConvertUTF16toUTF8(objectPath).get(), DBUS_DEVICE_IFACE, "DiscoverServices", DBUS_TYPE_STRING, &EmptyCString(), DBUS_TYPE_INVALID); + return NS_OK; } protected: static void DiscoverServicesCallback(DBusMessage* aMsg, void* aData) { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread RefPtr<OnUpdateSdpRecordsRunnable> r( @@ -3758,18 +3786,18 @@ private: }; bool BluetoothDBusService::UpdateSdpRecords(const BluetoothAddress& aDeviceAddress, BluetoothProfileManagerBase* aManager) { MOZ_ASSERT(NS_IsMainThread()); - Task* task = new UpdateSdpRecordsTask(aDeviceAddress, aManager); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<UpdateSdpRecordsTask>(aDeviceAddress, aManager)); return true; } void BluetoothDBusService::SendFile(const BluetoothAddress& aDeviceAddress, BlobParent* aBlobParent, BlobChild* aBlobChild, @@ -3887,17 +3915,17 @@ BluetoothDBusService::IsScoConnected(Blu NS_NAMED_LITERAL_STRING(replyError, "Fail to get BluetoothHfpManager"); DispatchBluetoothReply(aRunnable, BluetoothValue(), replyError); return; } DispatchBluetoothReply(aRunnable, hfp->IsScoConnected(), EmptyString()); } -class SendMetadataTask : public Task +class SendMetadataTask : public Runnable { public: SendMetadataTask(const BluetoothAddress& aDeviceAddress, const nsACString& aTitle, const nsACString& aArtist, const nsACString& aAlbum, int64_t aMediaNumber, int64_t aTotalMediaCount, @@ -3911,17 +3939,17 @@ public: , mTotalMediaCount(aTotalMediaCount) , mDuration(aDuration) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); // We currently don't support genre field in music player. // In order to send media metadata through AVRCP, we set genre to an empty // string to match the BlueZ method "UpdateMetaData" with signature @@ -3960,19 +3988,21 @@ public: DBUS_TYPE_STRING, &title, DBUS_TYPE_STRING, &artist, DBUS_TYPE_STRING, &album, DBUS_TYPE_STRING, &mediaNumber, DBUS_TYPE_STRING, &totalMediaCount, DBUS_TYPE_STRING, &duration, DBUS_TYPE_STRING, &genre, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << mRunnable.forget(); // picked up by callback handler + + return NS_OK; } private: const BluetoothAddress mDeviceAddress; const nsCString mTitle; const nsCString mArtist; const nsCString mAlbum; int64_t mMediaNumber; @@ -4016,32 +4046,32 @@ BluetoothDBusService::SendMetaData(const !aTitle.Equals(prevTitle) || !aAlbum.Equals(prevAlbum)) { UpdateNotification(ControlEventId::EVENT_TRACK_CHANGED, aMediaNumber); } BluetoothAddress deviceAddress; avrcp->GetAddress(deviceAddress); - Task* task = new SendMetadataTask( - deviceAddress, - NS_ConvertUTF16toUTF8(aTitle), - NS_ConvertUTF16toUTF8(aArtist), - NS_ConvertUTF16toUTF8(aAlbum), - aMediaNumber, - aTotalMediaCount, - aDuration, - aRunnable); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<SendMetadataTask>( + deviceAddress, + NS_ConvertUTF16toUTF8(aTitle), + NS_ConvertUTF16toUTF8(aArtist), + NS_ConvertUTF16toUTF8(aAlbum), + aMediaNumber, + aTotalMediaCount, + aDuration, + aRunnable)); avrcp->UpdateMetaData(aTitle, aArtist, aAlbum, aMediaNumber, aTotalMediaCount, aDuration); } -class SendPlayStatusTask : public Task +class SendPlayStatusTask : public Runnable { public: SendPlayStatusTask(const BluetoothAddress& aDeviceAddress, int64_t aDuration, int64_t aPosition, ControlPlayStatus aPlayStatus, BluetoothReplyRunnable* aRunnable) : mDeviceAddress(aDeviceAddress) @@ -4049,17 +4079,17 @@ public: , mPosition(aPosition) , mPlayStatus(aPlayStatus) , mRunnable(aRunnable) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); MOZ_ASSERT(mRunnable); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); @@ -4069,19 +4099,21 @@ public: GetVoidCallback, static_cast<void*>(mRunnable.get()), -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdatePlayStatus", DBUS_TYPE_UINT32, &mDuration, DBUS_TYPE_UINT32, &mPosition, DBUS_TYPE_UINT32, &tempPlayStatus, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); Unused << mRunnable.forget(); // picked up by callback handler + + return NS_OK; } private: const BluetoothAddress mDeviceAddress; int64_t mDuration; int64_t mPosition; ControlPlayStatus mPlayStatus; RefPtr<BluetoothReplyRunnable> mRunnable; @@ -4129,22 +4161,22 @@ BluetoothDBusService::SendPlayStatus(int aPlayStatus); } else if (aPosition != avrcp->GetPosition()) { UpdateNotification(ControlEventId::EVENT_PLAYBACK_POS_CHANGED, aPosition); } BluetoothAddress deviceAddress; avrcp->GetAddress(deviceAddress); - Task* task = new SendPlayStatusTask(deviceAddress, + DispatchToDBusThread( + MakeAndAddRef<SendPlayStatusTask>(deviceAddress, aDuration, aPosition, aPlayStatus, - aRunnable); - DispatchToDBusThread(task); + aRunnable)); avrcp->UpdatePlayStatus(aDuration, aPosition, aPlayStatus); } static void ControlCallback(DBusMessage* aMsg, void* aParam) { NS_ENSURE_TRUE_VOID(aMsg); @@ -4152,32 +4184,32 @@ ControlCallback(DBusMessage* aMsg, void* BluetoothValue v; nsAutoString replyError; UnpackVoidMessage(aMsg, nullptr, v, replyError); if (!v.get_bool()) { BT_WARNING(NS_ConvertUTF16toUTF8(replyError).get()); } } -class UpdatePlayStatusTask : public Task +class UpdatePlayStatusTask : public Runnable { public: UpdatePlayStatusTask(const BluetoothAddress& aDeviceAddress, int32_t aDuration, int32_t aPosition, ControlPlayStatus aPlayStatus) : mDeviceAddress(aDeviceAddress) , mDuration(aDuration) , mPosition(aPosition) , mPlayStatus(aPlayStatus) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); @@ -4187,17 +4219,19 @@ public: ControlCallback, nullptr, -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdatePlayStatus", DBUS_TYPE_UINT32, &mDuration, DBUS_TYPE_UINT32, &mPosition, DBUS_TYPE_UINT32, &tempPlayStatus, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); + + return NS_OK; } private: const BluetoothAddress mDeviceAddress; int32_t mDuration; int32_t mPosition; ControlPlayStatus mPlayStatus; }; @@ -4213,37 +4247,37 @@ BluetoothDBusService::UpdatePlayStatus(u BluetoothAvrcpManager* avrcp = BluetoothAvrcpManager::Get(); NS_ENSURE_TRUE_VOID(avrcp); MOZ_ASSERT(avrcp->IsConnected()); MOZ_ASSERT(!sAdapterPath.IsEmpty()); BluetoothAddress deviceAddress; avrcp->GetAddress(deviceAddress); - Task* task = new UpdatePlayStatusTask(deviceAddress, + DispatchToDBusThread( + MakeAndAddRef<UpdatePlayStatusTask>(deviceAddress, aDuration, aPosition, - aPlayStatus); - DispatchToDBusThread(task); + aPlayStatus)); } -class UpdateNotificationTask : public Task +class UpdateNotificationTask : public Runnable { public: UpdateNotificationTask(const BluetoothAddress& aDeviceAddress, BluetoothDBusService::ControlEventId aEventId, uint64_t aData) : mDeviceAddress(aDeviceAddress) , mEventId(aEventId) , mData(aData) { MOZ_ASSERT(!mDeviceAddress.IsCleared()); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!NS_IsMainThread()); // I/O thread MOZ_ASSERT(sDBusConnection); MOZ_ASSERT(!sAdapterPath.IsEmpty()); const nsCString objectPath = NS_ConvertUTF16toUTF8( GetObjectPathFromAddress(sAdapterPath, mDeviceAddress)); @@ -4252,17 +4286,19 @@ public: bool success = sDBusConnection->SendWithReply( ControlCallback, nullptr, -1, BLUEZ_DBUS_BASE_IFC, objectPath.get(), DBUS_CTL_IFACE, "UpdateNotification", DBUS_TYPE_UINT16, &eventId, DBUS_TYPE_UINT64, &mData, DBUS_TYPE_INVALID); - NS_ENSURE_TRUE_VOID(success); + NS_ENSURE_TRUE(success, NS_OK); + + return NS_OK; } private: const BluetoothAddress mDeviceAddress; int16_t mEventId; int32_t mData; }; @@ -4276,18 +4312,18 @@ BluetoothDBusService::UpdateNotification BluetoothAvrcpManager* avrcp = BluetoothAvrcpManager::Get(); NS_ENSURE_TRUE_VOID(avrcp); MOZ_ASSERT(avrcp->IsConnected()); MOZ_ASSERT(!sAdapterPath.IsEmpty()); BluetoothAddress deviceAddress; avrcp->GetAddress(deviceAddress); - Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData); - DispatchToDBusThread(task); + DispatchToDBusThread( + MakeAndAddRef<UpdateNotificationTask>(deviceAddress, aEventId, aData)); } void BluetoothDBusService::StartLeScanInternal( const nsTArray<BluetoothUuid>& aServiceUuids, BluetoothReplyRunnable* aRunnable) { }
--- a/dom/bluetooth/bluez/BluetoothHfpManager.cpp +++ b/dom/bluetooth/bluez/BluetoothHfpManager.cpp @@ -237,85 +237,88 @@ BluetoothHfpManager::Notify(const hal::B int level = round(aBatteryInfo.level() * 5.0); if (level != sCINDItems[CINDType::BATTCHG].value) { sCINDItems[CINDType::BATTCHG].value = level; SendCommand(RESPONSE_CIEV, CINDType::BATTCHG); } } #ifdef MOZ_B2G_RIL -class BluetoothHfpManager::RespondToBLDNTask : public Task +class BluetoothHfpManager::RespondToBLDNTask : public Runnable { private: - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(sBluetoothHfpManager); if (!sBluetoothHfpManager->mDialingRequestProcessed) { sBluetoothHfpManager->mDialingRequestProcessed = true; sBluetoothHfpManager->SendLine("ERROR"); } + return NS_OK; } }; -class BluetoothHfpManager::SendRingIndicatorTask : public Task +class BluetoothHfpManager::SendRingIndicatorTask : public Runnable { public: SendRingIndicatorTask(const nsAString& aNumber, int aType) : mNumber(aNumber) , mType(aType) { MOZ_ASSERT(NS_IsMainThread()); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); // Stop sending RING indicator if (sStopSendingRingFlag) { - return; + return NS_OK; } if (!sBluetoothHfpManager) { BT_WARNING("BluetoothHfpManager no longer exists, cannot send ring!"); - return; + return NS_OK; } nsAutoCString ringMsg("RING"); sBluetoothHfpManager->SendLine(ringMsg.get()); if (!mNumber.IsEmpty()) { nsAutoCString clipMsg("+CLIP: \""); clipMsg.Append(NS_ConvertUTF16toUTF8(mNumber).get()); clipMsg.AppendLiteral("\","); clipMsg.AppendInt(mType); sBluetoothHfpManager->SendLine(clipMsg.get()); } - MessageLoop::current()-> - PostDelayedTask(FROM_HERE, - new SendRingIndicatorTask(mNumber, mType), - sRingInterval); + MessageLoop::current()->PostDelayedTask( + MakeAndAddRef<SendRingIndicatorTask>(mNumber, mType), sRingInterval); + + return NS_OK; } private: nsString mNumber; int mType; }; #endif // MOZ_B2G_RIL -class BluetoothHfpManager::CloseScoTask : public Task +class BluetoothHfpManager::CloseScoTask : public Runnable { private: - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(sBluetoothHfpManager); sBluetoothHfpManager->DisconnectSco(); + + return NS_OK; } }; #ifdef MOZ_B2G_RIL static bool IsValidDtmf(const char aChar) { // Valid DTMF: [*#0-9ABCD] if (aChar == '*' || aChar == '#') { @@ -980,17 +983,17 @@ BluetoothHfpManager::ReceiveSocketData(B if (msg.Find("AT+BLDN") != -1) { NotifyDialer(NS_LITERAL_STRING("BLDN")); } else { NotifyDialer(NS_ConvertUTF8toUTF16(msg)); } MessageLoop::current()-> - PostDelayedTask(FROM_HERE, new RespondToBLDNTask(), + PostDelayedTask(MakeAndAddRef<RespondToBLDNTask>(), sWaitingForDialingInterval); // Don't send response 'OK' here because we'll respond later in either // RespondToBLDNTask or HandleCallStateChanged() return; } else if (msg.Find("ATA") != -1) { NotifyDialer(NS_LITERAL_STRING("ATA")); } else if (msg.Find("AT+CHUP") != -1) { @@ -1571,19 +1574,18 @@ BluetoothHfpManager::HandleCallStateChan } nsAutoString number(aNumber); if (!mCLIP) { number.Truncate(); } MessageLoop::current()->PostDelayedTask( - FROM_HERE, - new SendRingIndicatorTask(number, - mCurrentCallArray[aCallIndex].mType), + MakeAndAddRef<SendRingIndicatorTask>( + number, mCurrentCallArray[aCallIndex].mType), sRingInterval); } break; case nsITelephonyService::CALL_STATE_DIALING: if (!mDialingRequestProcessed) { SendLine("OK"); mDialingRequestProcessed = true; } @@ -1681,18 +1683,17 @@ BluetoothHfpManager::HandleCallStateChan if (mCurrentCallArray.Length() - 1 == GetNumberOfCalls(nsITelephonyService::CALL_STATE_DISCONNECTED)) { // In order to let user hear busy tone via connected Bluetooth headset, // we postpone the timing of dropping SCO. if (!(aError.Equals(NS_LITERAL_STRING("BusyError")))) { DisconnectSco(); } else { // Close Sco later since Dialer is still playing busy tone via HF. - MessageLoop::current()->PostDelayedTask(FROM_HERE, - new CloseScoTask(), + MessageLoop::current()->PostDelayedTask(MakeAndAddRef<CloseScoTask>(), sBusyToneInterval); } ResetCallArray(); } break; default: BT_WARNING("Not handling state changed");
--- a/dom/bluetooth/bluez/BluetoothOppManager.cpp +++ b/dom/bluetooth/bluez/BluetoothOppManager.cpp @@ -162,32 +162,33 @@ public: return NS_OK; }; private: nsCOMPtr<nsIInputStream> mInputStream; uint32_t mAvailablePacketSize; }; -class CloseSocketTask : public Task +class CloseSocketTask : public Runnable { public: CloseSocketTask(BluetoothSocket* aSocket) : mSocket(aSocket) { MOZ_ASSERT(aSocket); } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(NS_IsMainThread()); if (mSocket->GetConnectionStatus() == SocketConnectionStatus::SOCKET_CONNECTED) { mSocket->Close(); } + return NS_OK; } private: RefPtr<BluetoothSocket> mSocket; }; BluetoothOppManager::BluetoothOppManager() : mConnected(false) @@ -1071,17 +1072,17 @@ BluetoothOppManager::ClientDataHandler(U FileTransferComplete(); } else if (mLastCommand == ObexRequestCode::Disconnect) { AfterOppDisconnected(); // Most devices will directly terminate connection after receiving // Disconnect request, so we make a delay here. If the socket hasn't been // disconnected, we will close it. if (mSocket) { MessageLoop::current()-> - PostDelayedTask(FROM_HERE, new CloseSocketTask(mSocket), 1000); + PostDelayedTask(MakeAndAddRef<CloseSocketTask>(mSocket), 1000); } } else if (mLastCommand == ObexRequestCode::Connect) { MOZ_ASSERT(!mFileName.IsEmpty()); MOZ_ASSERT(mBlob); AfterOppConnected(); // Ensure valid access to remote information
--- a/dom/bluetooth/bluez/BluetoothSocket.cpp +++ b/dom/bluetooth/bluez/BluetoothSocket.cpp @@ -38,17 +38,17 @@ public: void GetSocketAddr(BluetoothAddress& aAddress) const; BluetoothSocket* GetBluetoothSocket(); DataSocket* GetDataSocket(); // Delayed-task handling // - void SetDelayedConnectTask(CancelableTask* aTask); + void SetDelayedConnectTask(CancelableRunnable* aTask); void ClearDelayedConnectTask(); void CancelDelayedConnectTask(); // Task callback methods // /** * Run bind/listen to prepare for further runs of accept() @@ -121,17 +121,17 @@ private: * Address structure of the socket currently in use */ struct sockaddr_storage mAddress; /** * Task member for delayed connect task. Should only be access on consumer * thread. */ - CancelableTask* mDelayedConnectTask; + CancelableRunnable* mDelayedConnectTask; /** * I/O buffer for received data */ UniquePtr<UnixSocketRawData> mBuffer; }; BluetoothSocket::BluetoothSocketIO::BluetoothSocketIO( @@ -188,17 +188,17 @@ BluetoothSocket::BluetoothSocketIO::GetB DataSocket* BluetoothSocket::BluetoothSocketIO::GetDataSocket() { return GetBluetoothSocket(); } void -BluetoothSocket::BluetoothSocketIO::SetDelayedConnectTask(CancelableTask* aTask) +BluetoothSocket::BluetoothSocketIO::SetDelayedConnectTask(CancelableRunnable* aTask) { MOZ_ASSERT(IsConsumerThread()); mDelayedConnectTask = aTask; } void BluetoothSocket::BluetoothSocketIO::ClearDelayedConnectTask() @@ -280,17 +280,17 @@ BluetoothSocket::BluetoothSocketIO::Send void BluetoothSocket::BluetoothSocketIO::OnConnected() { MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop()); MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); GetConsumerThread()->PostTask( - FROM_HERE, new SocketEventTask(this, SocketEventTask::CONNECT_SUCCESS)); + MakeAndAddRef<SocketEventTask>(this, SocketEventTask::CONNECT_SUCCESS)); AddWatchers(READ_WATCHER, true); if (HasPendingData()) { AddWatchers(WRITE_WATCHER, false); } } void @@ -329,17 +329,17 @@ BluetoothSocket::BluetoothSocketIO::OnSo FireSocketError(); return; } Close(); SetSocket(fd, SOCKET_IS_CONNECTED); GetConsumerThread()->PostTask( - FROM_HERE, new SocketEventTask(this, SocketEventTask::CONNECT_SUCCESS)); + MakeAndAddRef<SocketEventTask>(this, SocketEventTask::CONNECT_SUCCESS)); AddWatchers(READ_WATCHER, true); if (HasPendingData()) { AddWatchers(WRITE_WATCHER, false); } } void @@ -379,17 +379,17 @@ BluetoothSocket::BluetoothSocketIO::Fire { MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop()); // Clean up watchers, statuses, fds Close(); // Tell the consumer thread we've errored GetConsumerThread()->PostTask( - FROM_HERE, new SocketEventTask(this, SocketEventTask::CONNECT_ERROR)); + MakeAndAddRef<SocketEventTask>(this, SocketEventTask::CONNECT_ERROR)); } // |DataSocketIO| nsresult BluetoothSocket::BluetoothSocketIO::QueryReceiveBuffer( UnixSocketIOBuffer** aBuffer) { @@ -411,43 +411,45 @@ class BluetoothSocket::BluetoothSocketIO : public SocketTask<BluetoothSocketIO> { public: ReceiveTask(BluetoothSocketIO* aIO, UnixSocketBuffer* aBuffer) : SocketTask<BluetoothSocketIO>(aIO) , mBuffer(aBuffer) { } - void Run() override + NS_IMETHOD Run() override { BluetoothSocketIO* io = SocketTask<BluetoothSocketIO>::GetIO(); MOZ_ASSERT(io->IsConsumerThread()); if (NS_WARN_IF(io->IsShutdownOnConsumerThread())) { // Since we've already explicitly closed and the close // happened before this, this isn't really an error. - return; + return NS_OK; } BluetoothSocket* bluetoothSocket = io->GetBluetoothSocket(); MOZ_ASSERT(bluetoothSocket); bluetoothSocket->ReceiveSocketData(mBuffer); + + return NS_OK; } private: UniquePtr<UnixSocketBuffer> mBuffer; }; void BluetoothSocket::BluetoothSocketIO::ConsumeBuffer() { - GetConsumerThread()->PostTask(FROM_HERE, - new ReceiveTask(this, mBuffer.release())); + GetConsumerThread()->PostTask( + MakeAndAddRef<ReceiveTask>(this, mBuffer.release())); } void BluetoothSocket::BluetoothSocketIO::DiscardBuffer() { // Nothing to do. } @@ -500,66 +502,71 @@ BluetoothSocket::BluetoothSocketIO::Shut class BluetoothSocket::ListenTask final : public SocketIOTask<BluetoothSocketIO> { public: ListenTask(BluetoothSocketIO* aIO) : SocketIOTask<BluetoothSocketIO>(aIO) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!GetIO()->IsConsumerThread()); if (!IsCanceled()) { GetIO()->Listen(); } + return NS_OK; } }; class BluetoothSocket::ConnectTask final : public SocketIOTask<BluetoothSocketIO> { public: ConnectTask(BluetoothSocketIO* aIO) : SocketIOTask<BluetoothSocketIO>(aIO) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(!GetIO()->IsConsumerThread()); MOZ_ASSERT(!IsCanceled()); GetIO()->Connect(); + + return NS_OK; } }; class BluetoothSocket::DelayedConnectTask final : public SocketIOTask<BluetoothSocketIO> { public: DelayedConnectTask(BluetoothSocketIO* aIO) : SocketIOTask<BluetoothSocketIO>(aIO) { } - void Run() override + NS_IMETHOD Run() override { MOZ_ASSERT(GetIO()->IsConsumerThread()); if (IsCanceled()) { - return; + return NS_OK; } BluetoothSocketIO* io = GetIO(); if (io->IsShutdownOnConsumerThread()) { - return; + return NS_OK; } io->ClearDelayedConnectTask(); - io->GetIOLoop()->PostTask(FROM_HERE, new ConnectTask(io)); + io->GetIOLoop()->PostTask(MakeAndAddRef<ConnectTask>(io)); + + return NS_OK; } }; // // BluetoothSocket // BluetoothSocket::BluetoothSocket(BluetoothSocketObserver* aObserver) @@ -665,21 +672,22 @@ BluetoothSocket::Connect(BluetoothUnixSo MOZ_ASSERT(aConsumerLoop); MOZ_ASSERT(aIOLoop); MOZ_ASSERT(!mIO); mIO = new BluetoothSocketIO(aConsumerLoop, aIOLoop, this, aConnector); SetConnectionStatus(SOCKET_CONNECTING); if (aDelayMs > 0) { - DelayedConnectTask* connectTask = new DelayedConnectTask(mIO); + RefPtr<DelayedConnectTask> connectTask = + MakeAndAddRef<DelayedConnectTask>(mIO); mIO->SetDelayedConnectTask(connectTask); - MessageLoop::current()->PostDelayedTask(FROM_HERE, connectTask, aDelayMs); + MessageLoop::current()->PostDelayedTask(connectTask.forget(), aDelayMs); } else { - aIOLoop->PostTask(FROM_HERE, new ConnectTask(mIO)); + aIOLoop->PostTask(MakeAndAddRef<ConnectTask>(mIO)); } return NS_OK; } nsresult BluetoothSocket::Connect(BluetoothUnixSocketConnector* aConnector, int aDelayMs) @@ -695,17 +703,17 @@ BluetoothSocket::Listen(BluetoothUnixSoc MOZ_ASSERT(aConnector); MOZ_ASSERT(aConsumerLoop); MOZ_ASSERT(aIOLoop); MOZ_ASSERT(!mIO); mIO = new BluetoothSocketIO(aConsumerLoop, aIOLoop, this, aConnector); SetConnectionStatus(SOCKET_LISTENING); - aIOLoop->PostTask(FROM_HERE, new ListenTask(mIO)); + aIOLoop->PostTask(MakeAndAddRef<ListenTask>(mIO)); return NS_OK; } nsresult BluetoothSocket::Listen(BluetoothUnixSocketConnector* aConnector) { return Listen(aConnector, MessageLoop::current(), XRE_GetIOMessageLoop()); @@ -728,18 +736,17 @@ BluetoothSocket::GetAddress(BluetoothAdd void BluetoothSocket::SendSocketData(UnixSocketIOBuffer* aBuffer) { MOZ_ASSERT(mIO); MOZ_ASSERT(mIO->IsConsumerThread()); MOZ_ASSERT(!mIO->IsShutdownOnConsumerThread()); mIO->GetIOLoop()->PostTask( - FROM_HERE, - new SocketIOSendTask<BluetoothSocketIO, UnixSocketIOBuffer>(mIO, aBuffer)); + MakeAndAddRef<SocketIOSendTask<BluetoothSocketIO, UnixSocketIOBuffer>>(mIO, aBuffer)); } // |SocketBase| void BluetoothSocket::Close() { if (!mIO) { @@ -749,17 +756,17 @@ BluetoothSocket::Close() MOZ_ASSERT(mIO->IsConsumerThread()); mIO->CancelDelayedConnectTask(); // From this point on, we consider mIO as being deleted. // We sever the relationship here so any future calls to listen or connect // will create a new implementation. mIO->ShutdownOnConsumerThread(); - mIO->GetIOLoop()->PostTask(FROM_HERE, new SocketIOShutdownTask(mIO)); + mIO->GetIOLoop()->PostTask(MakeAndAddRef<SocketIOShutdownTask>(mIO)); mIO = nullptr; NotifyDisconnect(); } void BluetoothSocket::OnConnectSuccess() {
--- a/dom/broadcastchannel/BroadcastChannel.cpp +++ b/dom/broadcastchannel/BroadcastChannel.cpp @@ -68,17 +68,18 @@ GetPrincipalFromWorkerPrivate(WorkerPriv } class InitializeRunnable final : public WorkerMainThreadRunnable { public: InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsACString& aOrigin, PrincipalInfo& aPrincipalInfo, bool& aPrivateBrowsing, ErrorResult& aRv) - : WorkerMainThreadRunnable(aWorkerPrivate) + : WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("BroadcastChannel :: Initialize")) , mWorkerPrivate(GetCurrentThreadWorkerPrivate()) , mOrigin(aOrigin) , mPrincipalInfo(aPrincipalInfo) , mPrivateBrowsing(aPrivateBrowsing) , mRv(aRv) { MOZ_ASSERT(mWorkerPrivate); }
--- a/dom/canvas/ImageBitmap.cpp +++ b/dom/canvas/ImageBitmap.cpp @@ -264,17 +264,18 @@ class CreateImageFromRawDataInMainThread public: CreateImageFromRawDataInMainThreadSyncTask(uint8_t* aBuffer, uint32_t aBufferLength, uint32_t aStride, gfx::SurfaceFormat aFormat, const gfx::IntSize& aSize, const Maybe<IntRect>& aCropRect, layers::Image** aImage) - : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate()) + : WorkerMainThreadRunnable(GetCurrentThreadWorkerPrivate(), + NS_LITERAL_CSTRING("ImageBitmap :: Create Image from Raw Data")) , mImage(aImage) , mBuffer(aBuffer) , mBufferLength(aBufferLength) , mStride(aStride) , mFormat(aFormat) , mSize(aSize) , mCropRect(aCropRect) { @@ -1145,17 +1146,18 @@ class CreateImageBitmapFromBlobWorkerTas // This is a synchronous task. class DecodeBlobInMainThreadSyncTask final : public WorkerMainThreadRunnable { public: DecodeBlobInMainThreadSyncTask(WorkerPrivate* aWorkerPrivate, Blob& aBlob, Maybe<IntRect>& aCropRect, layers::Image** aImage) - : WorkerMainThreadRunnable(aWorkerPrivate) + : WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("ImageBitmap :: Create Image from Blob")) , mBlob(aBlob) , mCropRect(aCropRect) , mImage(aImage) { } bool MainThreadRun() override {
--- a/dom/downloads/tests/shim_app_as_test.js +++ b/dom/downloads/tests/shim_app_as_test.js @@ -1,16 +1,10 @@ /** - * Support logic to run a test file as an installed app. This file is derived - * from dom/requestsync/tests/test_basic_app.html but uses - * DOMApplicationRegistry in a chrome script (shim_app_as_test_chrome.js) to - * directly install the apps instead of mozApps.install because mozApps.install - * can't install privileged/certified apps. (This is the same mechanism used by - * the Firefox OS Gaia email app's backend test runner.) - * + * Support logic to run a test file as an installed app. * You really only want to do this if your test cares about the app's origin * or you REALLY want to double-check AvailableIn and other WebIDL-provided * security mechanisms. * * If you trust WebIDL, your life may be made significantly easier by just * setting the pref "dom.ignore_webidl_scope_checks" to true, which makes * BindingUtils.cpp's IsInPrivilegedApp and IsInCertifiedApp return true no * matter what *on the main thread*. You are potentially out of luck on
--- a/dom/events/DataTransfer.cpp +++ b/dom/events/DataTransfer.cpp @@ -967,17 +967,17 @@ DataTransfer::GetTransferables(nsIDOMNod { MOZ_ASSERT(aDragTarget); nsCOMPtr<nsINode> dragNode = do_QueryInterface(aDragTarget); if (!dragNode) { return nullptr; } - nsIDocument* doc = dragNode->GetUncomposedDoc(); + nsIDocument* doc = dragNode->GetComposedDoc(); if (!doc) { return nullptr; } return GetTransferables(doc->GetLoadContext()); } already_AddRefed<nsISupportsArray>
--- a/dom/events/test/mochitest.ini +++ b/dom/events/test/mochitest.ini @@ -192,8 +192,10 @@ skip-if = buildapp == 'b2g' || e10s [test_bug1096146.html] support-files = bug1096146_embedded.html [test_offsetxy.html] [test_eventhandler_scoping.html] [test_bug1013412.html] skip-if = buildapp == 'b2g' # no wheel events on b2g [test_dom_activate_event.html] +[test_bug1264380.html] +run-if = e10s # bug1264380 comment 20, nsDragService::InvokeDragSessionImpl behaves differently among platform implementations in non-e10s mode which prevents us to check the validity of nsIDragService::getCurrentSession() consistently via synthesize mouse clicks in non-e10s mode.
new file mode 100644 --- /dev/null +++ b/dom/events/test/test_bug1264380.html @@ -0,0 +1,54 @@ +<html> +<head> + <title>Test the dragstart event on the anchor in side shadow DOM</title> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script> +<script> + +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({"set": [ + ["dom.webcomponents.enabled", true] +]}); + +function runTests() +{ + let dragService = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"]. + getService(SpecialPowers.Ci.nsIDragService); + + let shadow = document.querySelector('#outter').createShadowRoot(); + let target = document.createElement('a'); + let linkText = document.createTextNode("Drag me if you can!"); + target.appendChild(linkText); + target.href = "http://www.mozilla.org/"; + shadow.appendChild(target); + + let dataTransfer; + let trapDrag = function(event) { + ok(true, "Got dragstart event"); + dataTransfer = event.dataTransfer; + ok(dataTransfer, "DataTransfer object is available."); + is(dataTransfer.mozItemCount, 1, "initial link item count"); + is(dataTransfer.getData("text/uri-list"), "http://www.mozilla.org/", "link text/uri-list"); + is(dataTransfer.getData("text/plain"), "http://www.mozilla.org/", "link text/plain"); + } + + ok(!dragService.getCurrentSession(), "There shouldn't be a drag session!"); + window.addEventListener("dragstart", trapDrag, true); + synthesizeMouse(target, 2, 2, { type: "mousedown" }); + synthesizeMouse(target, 11, 11, { type: "mousemove" }); + synthesizeMouse(target, 20, 20, { type: "mousemove" }); + window.removeEventListener("dragstart", trapDrag, true); + ok(dragService.getCurrentSession(), "Drag session is available."); + dragService.endDragSession(false); + ok(!dragService.getCurrentSession(), "There shouldn't be a drag session anymore!"); + SimpleTest.finish(); +} + +</script> + +<body onload="window.setTimeout(runTests, 0);"> +<div id="outter"/> +</body> +</html>
--- a/dom/fetch/Fetch.cpp +++ b/dom/fetch/Fetch.cpp @@ -713,17 +713,18 @@ public: }; template <class Derived> class CancelPumpRunnable final : public WorkerMainThreadRunnable { FetchBody<Derived>* mBody; public: explicit CancelPumpRunnable(FetchBody<Derived>* aBody) - : WorkerMainThreadRunnable(aBody->mWorkerPrivate) + : WorkerMainThreadRunnable(aBody->mWorkerPrivate, + NS_LITERAL_CSTRING("Fetch :: Cancel Pump")) , mBody(aBody) { } bool MainThreadRun() override { mBody->CancelPump(); return true;
--- a/dom/fetch/Request.cpp +++ b/dom/fetch/Request.cpp @@ -226,17 +226,18 @@ GetRequestURLFromWorker(const GlobalObje } class ReferrerSameOriginChecker final : public workers::WorkerMainThreadRunnable { public: ReferrerSameOriginChecker(workers::WorkerPrivate* aWorkerPrivate, const nsAString& aReferrerURL, nsresult& aResult) - : workers::WorkerMainThreadRunnable(aWorkerPrivate), + : workers::WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("Fetch :: Referrer same origin check")), mReferrerURL(aReferrerURL), mResult(aResult) { mWorkerPrivate->AssertIsOnWorkerThread(); } bool MainThreadRun() override
--- a/dom/html/HTMLSharedObjectElement.cpp +++ b/dom/html/HTMLSharedObjectElement.cpp @@ -15,18 +15,18 @@ #include "nsIDOMDocument.h" #include "nsThreadUtils.h" #include "nsIScriptError.h" #include "nsIWidget.h" #include "nsContentUtils.h" #ifdef XP_MACOSX #include "mozilla/EventDispatcher.h" #include "mozilla/dom/Event.h" +#endif #include "mozilla/dom/HTMLObjectElement.h" -#endif NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(SharedObject) namespace mozilla { namespace dom { HTMLSharedObjectElement::HTMLSharedObjectElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo, @@ -80,17 +80,17 @@ void HTMLSharedObjectElement::DoneAddingChildren(bool aHaveNotified) { if (!mIsDoneAddingChildren) { mIsDoneAddingChildren = true; // If we're already in a document, we need to trigger the load // Otherwise, BindToTree takes care of that. if (IsInComposedDoc()) { - StartObjectLoad(aHaveNotified); + StartObjectLoad(aHaveNotified, false); } } } NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLSharedObjectElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLSharedObjectElement, nsGenericHTMLElement) @@ -317,26 +317,26 @@ HTMLSharedObjectElement::GetAttributeMap if (mNodeInfo->Equals(nsGkAtoms::embed)) { return &MapAttributesIntoRuleExceptHidden; } return &MapAttributesIntoRule; } void -HTMLSharedObjectElement::StartObjectLoad(bool aNotify) +HTMLSharedObjectElement::StartObjectLoad(bool aNotify, bool aForceLoad) { // BindToTree can call us asynchronously, and we may be removed from the tree // in the interim if (!IsInComposedDoc() || !OwnerDoc()->IsActive() || BlockEmbedContentLoading()) { return; } - LoadObject(aNotify); + LoadObject(aNotify, aForceLoad); SetIsNetworkCreated(false); } EventStates HTMLSharedObjectElement::IntrinsicState() const { return nsGenericHTMLElement::IntrinsicState() | ObjectState(); } @@ -411,14 +411,23 @@ HTMLSharedObjectElement::BlockEmbedConte return false; } // Traverse up the node tree to see if we have any ancestors that may block us // from loading for (nsIContent* parent = GetParent(); parent; parent = parent->GetParent()) { if (parent->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) { return true; } + // If we have an ancestor that is an object with a source, it'll have an + // associated displayed type. If that type is not null, don't load content + // for the embed. + if (HTMLObjectElement* object = HTMLObjectElement::FromContent(parent)) { + uint32_t type = object->DisplayedType(); + if (type != eType_Null) { + return true; + } + } } return false; } } // namespace dom } // namespace mozilla
--- a/dom/html/HTMLSharedObjectElement.h +++ b/dom/html/HTMLSharedObjectElement.h @@ -75,17 +75,17 @@ public: // nsObjectLoadingContent virtual uint32_t GetCapabilities() const override; virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override; nsresult CopyInnerTo(Element* aDest); - void StartObjectLoad() { StartObjectLoad(true); } + void StartObjectLoad() { StartObjectLoad(true, false); } NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(HTMLSharedObjectElement, nsGenericHTMLElement) // WebIDL API for <applet> void GetAlign(DOMString& aValue) { GetHTMLAttr(nsGkAtoms::align, aValue); @@ -187,23 +187,22 @@ public: // height covered by <applet> // align covered by <applet> // name covered by <applet> nsIDocument* GetSVGDocument() { return GetContentDocument(); } -private: - virtual ~HTMLSharedObjectElement(); - /** * Calls LoadObject with the correct arguments to start the plugin load. */ - void StartObjectLoad(bool aNotify); + void StartObjectLoad(bool aNotify, bool aForceLoad); +private: + virtual ~HTMLSharedObjectElement(); nsIAtom *URIAttrName() const { return mNodeInfo->Equals(nsGkAtoms::applet) ? nsGkAtoms::code : nsGkAtoms::src; } @@ -223,16 +222,18 @@ private: /** * Decides whether we should load embed node content. * * If this is an embed node there are cases in which we should not try to load * the content: * * - If the embed node is the child of a media element + * - If the embed node is the child of an object node that already has + * content being loaded. * * In these cases, this function will return false, which will cause * us to skip calling LoadObject. */ bool BlockEmbedContentLoading(); }; } // namespace dom
--- a/dom/html/nsHTMLDNSPrefetch.cpp +++ b/dom/html/nsHTMLDNSPrefetch.cpp @@ -315,17 +315,17 @@ nsHTMLDNSPrefetch::nsDeferrals::SubmitQu while (mHead != mTail) { nsCOMPtr<nsIContent> content = do_QueryReferent(mEntries[mTail].mElement); if (content) { nsCOMPtr<Link> link = do_QueryInterface(content); // Only prefetch here if request was deferred and deferral not cancelled if (link && link->HasDeferredDNSPrefetchRequest()) { nsCOMPtr<nsIURI> hrefURI(link ? link->GetURI() : nullptr); bool isLocalResource = false; - nsresult rv; + nsresult rv = NS_OK; hostName.Truncate(); if (hrefURI) { hrefURI->GetAsciiHost(hostName); rv = NS_URIChainHasFlags(hrefURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &isLocalResource); }
--- a/dom/indexedDB/IDBDatabase.cpp +++ b/dom/indexedDB/IDBDatabase.cpp @@ -676,17 +676,17 @@ IDBDatabase::Transaction(JSContext* aCx, case IDBTransactionMode::Readwriteflush: mode = IDBTransaction::READ_WRITE_FLUSH; break; case IDBTransactionMode::Cleanup: mode = IDBTransaction::CLEANUP; mQuotaExceeded = false; break; case IDBTransactionMode::Versionchange: - return NS_ERROR_DOM_INVALID_ACCESS_ERR; + return NS_ERROR_DOM_TYPE_ERR; default: MOZ_CRASH("Unknown mode!"); } RefPtr<IDBTransaction> transaction = IDBTransaction::Create(aCx, this, sortedStoreNames, mode); if (NS_WARN_IF(!transaction)) {
--- a/dom/indexedDB/test/unit/test_transaction_error.js +++ b/dom/indexedDB/test/unit/test_transaction_error.js @@ -30,16 +30,24 @@ function testSteps() { request.onupgradeneeded = unexpectedSuccessHandler; request.onsuccess = grabEventAndContinueHandler; event = yield undefined; db = event.target.result; + try { + db.transaction(objectStoreName, "versionchange"); + ok(false, "TypeError shall be thrown if transaction mode is wrong."); + } catch (e) { + ok(e instanceof DOMException, "got a database exception"); + is(e.name, "TypeError", "correct error"); + } + let transaction = db.transaction(objectStoreName, "readwrite"); transaction.onerror = grabEventAndContinueHandler; transaction.oncomplete = grabEventAndContinueHandler; objectStore = transaction.objectStore(objectStoreName); info("Adding duplicate entry with preventDefault()");
--- a/dom/ipc/NuwaChild.cpp +++ b/dom/ipc/NuwaChild.cpp @@ -89,17 +89,17 @@ void NuwaFork() { if (sNuwaForking) { // No reentry. return; } sNuwaForking = true; MessageLoop* ioloop = XRE_GetIOMessageLoop(); - ioloop->PostTask(FROM_HERE, NewRunnableFunction(RunNuwaFork)); + ioloop->PostTask(NewRunnableFunction(RunNuwaFork)); } } // Anonymous namespace. #endif NuwaChild* NuwaChild::sSingleton;
--- a/dom/ipc/PreallocatedProcessManager.cpp +++ b/dom/ipc/PreallocatedProcessManager.cpp @@ -63,17 +63,17 @@ public: void OnNuwaReady(); bool PreallocatedProcessReady(); already_AddRefed<ContentParent> GetSpareProcess(); private: void NuwaFork(); // initialization off the critical path of app startup. - CancelableTask* mPreallocateAppProcessTask; + CancelableRunnable* mPreallocateAppProcessTask; // The array containing the preallocated processes. 4 as the inline storage size // should be enough so we don't need to grow the AutoTArray. AutoTArray<RefPtr<ContentParent>, 4> mSpareProcesses; // Nuwa process is ready for creating new process. bool mIsNuwaReady; #endif @@ -238,17 +238,17 @@ PreallocatedProcessManagerImpl::Schedule { MOZ_ASSERT(NS_IsMainThread()); if (mPreallocateAppProcessTask) { // Make sure there is only one request running. return; } - RefPtr<CancelableTask> task = NewRunnableMethod( + RefPtr<CancelableRunnable> task = NewRunnableMethod( this, &PreallocatedProcessManagerImpl::DelayedNuwaFork); mPreallocateAppProcessTask = task; MessageLoop::current()->PostDelayedTask(task.forget(), Preferences::GetUint("dom.ipc.processPrelaunch.delayMs", DEFAULT_ALLOCATE_DELAY)); } void
new file mode 100644 --- /dev/null +++ b/dom/media/MediaCallbackID.cpp @@ -0,0 +1,50 @@ +#include "MediaCallbackID.h" + +namespace mozilla { + +char const* CallbackID::INVALID_TAG = "INVALID_TAG"; +int32_t const CallbackID::INVALID_ID = -1; + +CallbackID::CallbackID() + : mTag(INVALID_TAG), mID(INVALID_ID) +{ +} + +CallbackID::CallbackID(char const* aTag, int32_t aID /* = 0*/) + : mTag(aTag), mID(aID) +{ +} + +CallbackID& +CallbackID::operator++() +{ + ++mID; + return *this; +} + +CallbackID +CallbackID::operator++(int) +{ + CallbackID ret = *this; + ++(*this); // call prefix++ + return ret; +} + +bool +CallbackID::operator==(const CallbackID& rhs) const +{ + return (strcmp(mTag, rhs.mTag) == 0) && (mID == rhs.mID); +} + +bool +CallbackID::operator!=(const CallbackID& rhs) const +{ + return !(*this == rhs); +} + +CallbackID::operator int() const +{ + return mID; +} + +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/dom/media/MediaCallbackID.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MediaCallbackID_h_ +#define MediaCallbackID_h_ + +namespace mozilla { + +struct CallbackID +{ + static char const* INVALID_TAG; + static int32_t const INVALID_ID; + + CallbackID(); + + explicit CallbackID(char const* aTag, int32_t aID = 0); + + CallbackID& operator++(); // prefix++ + + CallbackID operator++(int); // postfix++ + + bool operator==(const CallbackID& rhs) const; + + bool operator!=(const CallbackID& rhs) const; + + operator int() const; + +private: + char const* mTag; + int32_t mID; +}; + +} // namespace mozilla + +#endif // MediaCallbackID_h_
--- a/dom/media/MediaData.h +++ b/dom/media/MediaData.h @@ -217,18 +217,18 @@ private: // aligned and that it has sufficient end padding to allow for Alignment bytes // block read as required by some data decoders. // Returns false if memory couldn't be allocated. bool EnsureCapacity(size_t aLength) { const CheckedInt<size_t> sizeNeeded = CheckedInt<size_t>(aLength) * sizeof(Type) + AlignmentPaddingSize(); - if (!sizeNeeded.isValid()) { - // overflow. + if (!sizeNeeded.isValid() || sizeNeeded.value() >= INT32_MAX) { + // overflow or over an acceptable size. return false; } if (mData && mCapacity >= sizeNeeded.value()) { return true; } auto newBuffer = MakeUniqueFallible<uint8_t[]>(sizeNeeded.value()); if (!newBuffer) { return false;
--- a/dom/media/MediaDecoderReaderWrapper.cpp +++ b/dom/media/MediaDecoderReaderWrapper.cpp @@ -139,16 +139,18 @@ private: }; MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(bool aIsRealTime, AbstractThread* aOwnerThread, MediaDecoderReader* aReader) : mForceZeroStartTime(aIsRealTime || aReader->ForceZeroStartTime()) , mOwnerThread(aOwnerThread) , mReader(aReader) + , mAudioCallbackID("AudioCallbackID") + , mVideoCallbackID("VideoCallbackID") {} MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper() {} media::TimeUnit MediaDecoderReaderWrapper::StartTime() const { @@ -173,44 +175,76 @@ MediaDecoderReaderWrapper::ReadMetadata( RefPtr<HaveStartTimePromise> MediaDecoderReaderWrapper::AwaitStartTime() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_ASSERT(!mShutdown); return mStartTimeRendezvous->AwaitStartTime(); } -RefPtr<MediaDecoderReaderWrapper::MediaDataPromise> +void +MediaDecoderReaderWrapper::CancelAudioCallback(CallbackID aID) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(aID == mAudioCallbackID); + ++mAudioCallbackID; + mRequestAudioDataCB = nullptr; +} + +void +MediaDecoderReaderWrapper::CancelVideoCallback(CallbackID aID) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(aID == mVideoCallbackID); + ++mVideoCallbackID; + mRequestVideoDataCB = nullptr; +} + +void MediaDecoderReaderWrapper::RequestAudioData() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_ASSERT(!mShutdown); + MOZ_ASSERT(mRequestAudioDataCB, "Request audio data without callback!"); auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, &MediaDecoderReader::RequestAudioData); if (!mStartTimeRendezvous->HaveStartTime()) { p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(), &StartTimeRendezvous::ProcessFirstSample<MediaData::AUDIO_DATA>, &StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>) ->CompletionPromise(); } - return p->Then(mOwnerThread, __func__, this, - &MediaDecoderReaderWrapper::OnSampleDecoded, - &MediaDecoderReaderWrapper::OnNotDecoded) - ->CompletionPromise(); + RefPtr<MediaDecoderReaderWrapper> self = this; + mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__, + [self] (MediaData* aAudioSample) { + MOZ_ASSERT(self->mRequestAudioDataCB); + self->mAudioDataRequest.Complete(); + self->OnSampleDecoded(self->mRequestAudioDataCB.get(), aAudioSample, TimeStamp()); + }, + [self] (MediaDecoderReader::NotDecodedReason aReason) { + MOZ_ASSERT(self->mRequestAudioDataCB); + self->mAudioDataRequest.Complete(); + self->OnNotDecoded(self->mRequestAudioDataCB.get(), aReason); + })); } -RefPtr<MediaDecoderReaderWrapper::MediaDataPromise> +void MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); MOZ_ASSERT(!mShutdown); + MOZ_ASSERT(mRequestVideoDataCB, "Request video data without callback!"); + + // Time the video decode and send this value back to callbacks who accept + // a TimeStamp as its second parameter. + TimeStamp videoDecodeStartTime = TimeStamp::Now(); if (aTimeThreshold.ToMicroseconds() > 0 && mStartTimeRendezvous->HaveStartTime()) { aTimeThreshold += StartTime(); } auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, &MediaDecoderReader::RequestVideoData, @@ -218,20 +252,42 @@ MediaDecoderReaderWrapper::RequestVideoD if (!mStartTimeRendezvous->HaveStartTime()) { p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(), &StartTimeRendezvous::ProcessFirstSample<MediaData::VIDEO_DATA>, &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>) ->CompletionPromise(); } - return p->Then(mOwnerThread, __func__, this, - &MediaDecoderReaderWrapper::OnSampleDecoded, - &MediaDecoderReaderWrapper::OnNotDecoded) - ->CompletionPromise(); + RefPtr<MediaDecoderReaderWrapper> self = this; + mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__, + [self, videoDecodeStartTime] (MediaData* aVideoSample) { + MOZ_ASSERT(self->mRequestVideoDataCB); + self->mVideoDataRequest.Complete(); + self->OnSampleDecoded(self->mRequestVideoDataCB.get(), aVideoSample, videoDecodeStartTime); + }, + [self] (MediaDecoderReader::NotDecodedReason aReason) { + MOZ_ASSERT(self->mRequestVideoDataCB); + self->mVideoDataRequest.Complete(); + self->OnNotDecoded(self->mRequestVideoDataCB.get(), aReason); + })); +} + +bool +MediaDecoderReaderWrapper::IsRequestingAudioData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mAudioDataRequest.Exists(); +} + +bool +MediaDecoderReaderWrapper::IsRequestingVidoeData() const +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + return mVideoDataRequest.Exists(); } RefPtr<MediaDecoderReader::SeekPromise> MediaDecoderReaderWrapper::Seek(SeekTarget aTarget, media::TimeUnit aEndTime) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); aTarget.SetTime(aTarget.GetTime() + StartTime()); return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, @@ -272,25 +328,34 @@ MediaDecoderReaderWrapper::SetIdle() NS_NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle); mReader->OwnerThread()->Dispatch(r.forget()); } void MediaDecoderReaderWrapper::ResetDecode() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + + mAudioDataRequest.DisconnectIfExists(); + mVideoDataRequest.DisconnectIfExists(); + nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode); mReader->OwnerThread()->Dispatch(r.forget()); } RefPtr<ShutdownPromise> MediaDecoderReaderWrapper::Shutdown() { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestAudioDataCB); + MOZ_ASSERT(!mRequestVideoDataCB); + MOZ_ASSERT(!mAudioDataRequest.Exists()); + MOZ_ASSERT(!mVideoDataRequest.Exists()); + mShutdown = true; if (mStartTimeRendezvous) { mStartTimeRendezvous->Destroy(); mStartTimeRendezvous = nullptr; } return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__, &MediaDecoderReader::Shutdown); } @@ -318,17 +383,30 @@ MediaDecoderReaderWrapper::OnMetadataRea }, [] () { NS_WARNING("Setting start time on reader failed"); }); } } void -MediaDecoderReaderWrapper::OnSampleDecoded(MediaData* aSample) +MediaDecoderReaderWrapper::OnSampleDecoded(CallbackBase* aCallback, + MediaData* aSample, + TimeStamp aDecodeStartTime) { MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); - if (!mShutdown) { - aSample->AdjustForStartTime(StartTime().ToMicroseconds()); - } + MOZ_ASSERT(!mShutdown); + + aSample->AdjustForStartTime(StartTime().ToMicroseconds()); + aCallback->OnResolved(aSample, aDecodeStartTime); +} + +void +MediaDecoderReaderWrapper::OnNotDecoded(CallbackBase* aCallback, + MediaDecoderReader::NotDecodedReason aReason) +{ + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mShutdown); + + aCallback->OnRejected(aReason); } } // namespace mozilla
--- a/dom/media/MediaDecoderReaderWrapper.h +++ b/dom/media/MediaDecoderReaderWrapper.h @@ -7,16 +7,17 @@ #ifndef MediaDecoderReaderWrapper_h_ #define MediaDecoderReaderWrapper_h_ #include "mozilla/AbstractThread.h" #include "mozilla/RefPtr.h" #include "nsISupportsImpl.h" #include "MediaDecoderReader.h" +#include "MediaCallbackID.h" namespace mozilla { class StartTimeRendezvous; typedef MozPromise<bool, bool, /* isExclusive = */ false> HaveStartTimePromise; /** @@ -28,27 +29,230 @@ typedef MozPromise<bool, bool, /* isExcl class MediaDecoderReaderWrapper { typedef MediaDecoderReader::MetadataPromise MetadataPromise; typedef MediaDecoderReader::MediaDataPromise MediaDataPromise; typedef MediaDecoderReader::SeekPromise SeekPromise; typedef MediaDecoderReader::WaitForDataPromise WaitForDataPromise; typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise; NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper); + /* + * Type 1: void(MediaData*) + * void(RefPtr<MediaData>) + */ + template <typename T> + class ArgType1CheckHelper { + template<typename C, typename... Ts> + static TrueType + test(void(C::*aMethod)(Ts...), + decltype((DeclVal<C>().*aMethod)(DeclVal<MediaData*>()), 0)); + + template <typename F> + static TrueType + test(F&&, decltype(DeclVal<F>()(DeclVal<MediaData*>()), 0)); + + static FalseType test(...); + public: + typedef decltype(test(DeclVal<T>(), 0)) Type; + }; + + template <typename T> + struct ArgType1Check : public ArgType1CheckHelper<T>::Type {}; + + /* + * Type 2: void(MediaData*, TimeStamp) + * void(RefPtr<MediaData>, TimeStamp) + * void(MediaData*, TimeStamp&) + * void(RefPtr<MediaData>, const TimeStamp&&) + */ + template <typename T> + class ArgType2CheckHelper { + + template<typename C, typename... Ts> + static TrueType + test(void(C::*aMethod)(Ts...), + decltype((DeclVal<C>().*aMethod)(DeclVal<MediaData*>(), DeclVal<TimeStamp>()), 0)); + + template <typename F> + static TrueType + test(F&&, decltype(DeclVal<F>()(DeclVal<MediaData*>(), DeclVal<TimeStamp>()), 0)); + + static FalseType test(...); + public: + typedef decltype(test(DeclVal<T>(), 0)) Type; + }; + + template <typename T> + struct ArgType2Check : public ArgType2CheckHelper<T>::Type {}; + + struct CallbackBase + { + virtual ~CallbackBase() {} + virtual void OnResolved(MediaData*, TimeStamp) = 0; + virtual void OnRejected(MediaDecoderReader::NotDecodedReason) = 0; + }; + + template<typename ThisType, typename ResolveMethodType, typename RejectMethodType> + struct MethodCallback : public CallbackBase + { + MethodCallback(ThisType* aThis, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod) + : mThis(aThis), mResolveMethod(aResolveMethod), mRejectMethod(aRejectMethod) + { + } + + template<typename F> + typename EnableIf<ArgType1Check<F>::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp) + { + (mThis->*mResolveMethod)(aSample); + } + + template<typename F> + typename EnableIf<ArgType2Check<F>::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp aDecodeStartTime) + { + (mThis->*mResolveMethod)(aSample, aDecodeStartTime); + } + + void OnResolved(MediaData* aSample, TimeStamp aDecodeStartTime) override + { + CallHelper<ResolveMethodType>(aSample, aDecodeStartTime); + } + + void OnRejected(MediaDecoderReader::NotDecodedReason aReason) override + { + (mThis->*mRejectMethod)(aReason); + } + + RefPtr<ThisType> mThis; + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + }; + + template<typename ResolveFunctionType, typename RejectFunctionType> + struct FunctionCallback : public CallbackBase + { + FunctionCallback(ResolveFunctionType&& aResolveFuntion, RejectFunctionType&& aRejectFunction) + : mResolveFuntion(Move(aResolveFuntion)), mRejectFunction(Move(aRejectFunction)) + { + } + + template<typename F> + typename EnableIf<ArgType1Check<F>::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp) + { + mResolveFuntion(aSample); + } + + template<typename F> + typename EnableIf<ArgType2Check<F>::value, void>::Type + CallHelper(MediaData* aSample, TimeStamp aDecodeStartTime) + { + mResolveFuntion(aSample, aDecodeStartTime); + } + + void OnResolved(MediaData* aSample, TimeStamp aDecodeStartTime) override + { + CallHelper<ResolveFunctionType>(aSample, aDecodeStartTime); + } + + void OnRejected(MediaDecoderReader::NotDecodedReason aReason) override + { + mRejectFunction(aReason); + } + + ResolveFunctionType mResolveFuntion; + RejectFunctionType mRejectFunction; + }; + public: MediaDecoderReaderWrapper(bool aIsRealTime, AbstractThread* aOwnerThread, MediaDecoderReader* aReader); media::TimeUnit StartTime() const; RefPtr<MetadataPromise> ReadMetadata(); RefPtr<HaveStartTimePromise> AwaitStartTime(); - RefPtr<MediaDataPromise> RequestAudioData(); - RefPtr<MediaDataPromise> RequestVideoData(bool aSkipToNextKeyframe, - media::TimeUnit aTimeThreshold); + + template<typename ThisType, typename ResolveMethodType, typename RejectMethodType> + CallbackID + SetAudioCallback(ThisType* aThisVal, + ResolveMethodType aResolveMethod, + RejectMethodType aRejectMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestAudioDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestAudioDataCB.reset( + new MethodCallback<ThisType, ResolveMethodType, RejectMethodType>( + aThisVal, aResolveMethod, aRejectMethod)); + + return mAudioCallbackID; + } + + template<typename ResolveFunction, typename RejectFunction> + CallbackID + SetAudioCallback(ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestAudioDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestAudioDataCB.reset( + new FunctionCallback<ResolveFunction, RejectFunction>( + Move(aResolveFunction), Move(aRejectFunction))); + + return mAudioCallbackID; + } + + template<typename ThisType, typename ResolveMethodType, typename RejectMethodType> + CallbackID + SetVideoCallback(ThisType* aThisVal, + ResolveMethodType aResolveMethod, + RejectMethodType aRejectMethod) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestVideoDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestVideoDataCB.reset( + new MethodCallback<ThisType, ResolveMethodType, RejectMethodType>( + aThisVal, aResolveMethod, aRejectMethod)); + + return mVideoCallbackID; + } + + template<typename ResolveFunction, typename RejectFunction> + CallbackID + SetVideoCallback(ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction) + { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_ASSERT(!mRequestVideoDataCB, + "Please cancel the original callback before setting a new one."); + + mRequestVideoDataCB.reset( + new FunctionCallback<ResolveFunction, RejectFunction>( + Move(aResolveFunction), Move(aRejectFunction))); + + return mVideoCallbackID; + } + + void CancelAudioCallback(CallbackID aID); + void CancelVideoCallback(CallbackID aID); + + // NOTE: please set callbacks before requesting audio/video data! + void RequestAudioData(); + void RequestVideoData(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold); + + bool IsRequestingAudioData() const; + bool IsRequestingVidoeData() const; + RefPtr<SeekPromise> Seek(SeekTarget aTarget, media::TimeUnit aEndTime); RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType); RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise(); RefPtr<ShutdownPromise> Shutdown(); void ReleaseMediaResources(); void SetIdle(); void ResetDecode(); @@ -91,22 +295,35 @@ public: void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); } #endif private: ~MediaDecoderReaderWrapper(); void OnMetadataRead(MetadataHolder* aMetadata); void OnMetadataNotRead() {} - void OnSampleDecoded(MediaData* aSample); - void OnNotDecoded() {} + void OnSampleDecoded(CallbackBase* aCallback, MediaData* aSample, + TimeStamp aVideoDecodeStartTime); + void OnNotDecoded(CallbackBase* aCallback, + MediaDecoderReader::NotDecodedReason aReason); const bool mForceZeroStartTime; const RefPtr<AbstractThread> mOwnerThread; const RefPtr<MediaDecoderReader> mReader; bool mShutdown = false; RefPtr<StartTimeRendezvous> mStartTimeRendezvous; + + UniquePtr<CallbackBase> mRequestAudioDataCB; + UniquePtr<CallbackBase> mRequestVideoDataCB; + MozPromiseRequestHolder<MediaDataPromise> mAudioDataRequest; + MozPromiseRequestHolder<MediaDataPromise> mVideoDataRequest; + + /* + * These callback ids are used to prevent mis-canceling callback. + */ + CallbackID mAudioCallbackID; + CallbackID mVideoCallbackID; }; } // namespace mozilla #endif // MediaDecoderReaderWrapper_h_
--- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -347,16 +347,19 @@ MediaDecoderStateMachine::Initialization mWatchManager.Watch(mVideoCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus); mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged); mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged); mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged); mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration); mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration); mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration); mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged); + + // Configure MediaDecoderReaderWrapper. + SetMediaDecoderReaderWrapperCallback(); } media::MediaSink* MediaDecoderStateMachine::CreateAudioSink() { RefPtr<MediaDecoderStateMachine> self = this; auto audioSinkCreator = [self] () { MOZ_ASSERT(self->OnTaskQueue()); @@ -555,17 +558,16 @@ MediaDecoderStateMachine::NeedToDecodeAu void MediaDecoderStateMachine::OnAudioDecoded(MediaData* aAudioSample) { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState != DECODER_STATE_SEEKING); RefPtr<MediaData> audio(aAudioSample); MOZ_ASSERT(audio); - mAudioDataRequest.Complete(); // audio->GetEndTime() is not always mono-increasing in chained ogg. mDecodedAudioEndTime = std::max(audio->GetEndTime(), mDecodedAudioEndTime); SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d", (audio ? audio->mTime : -1), (audio ? audio->GetEndTime() : -1), (audio ? audio->mDiscontinuity : 0)); @@ -669,21 +671,16 @@ MediaDecoderStateMachine::OnNotDecoded(M { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState != DECODER_STATE_SEEKING); SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", aType, aReason); bool isAudio = aType == MediaData::AUDIO_DATA; MOZ_ASSERT_IF(!isAudio, aType == MediaData::VIDEO_DATA); - if (isAudio) { - mAudioDataRequest.Complete(); - } else { - mVideoDataRequest.Complete(); - } if (IsShutdown()) { // Already shutdown; return; } // If this is a decode error, delegate to the generic error path. if (aReason == MediaDecoderReader::DECODE_ERROR) { DecodeError(); @@ -788,17 +785,16 @@ void MediaDecoderStateMachine::OnVideoDecoded(MediaData* aVideoSample, TimeStamp aDecodeStartTime) { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState != DECODER_STATE_SEEKING); RefPtr<MediaData> video(aVideoSample); MOZ_ASSERT(video); - mVideoDataRequest.Complete(); // Handle abnormal or negative timestamps. mDecodedVideoEndTime = std::max(mDecodedVideoEndTime, video->GetEndTime()); SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d", (video ? video->mTime : -1), (video ? video->GetEndTime() : -1), (video ? video->mDiscontinuity : 0)); @@ -922,16 +918,43 @@ nsresult MediaDecoderStateMachine::Init( NS_ENSURE_SUCCESS(rv, rv); r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ReadMetadata); OwnerThread()->Dispatch(r.forget()); return NS_OK; } +void +MediaDecoderStateMachine::SetMediaDecoderReaderWrapperCallback() +{ + mAudioCallbackID = + mReader->SetAudioCallback(this, + &MediaDecoderStateMachine::OnAudioDecoded, + &MediaDecoderStateMachine::OnAudioNotDecoded); + + mVideoCallbackID = + mReader->SetVideoCallback(this, + &MediaDecoderStateMachine::OnVideoDecoded, + &MediaDecoderStateMachine::OnVideoNotDecoded); + + DECODER_LOG("MDSM set audio callbacks: mAudioCallbackID = %d\n", (int)mAudioCallbackID); + DECODER_LOG("MDSM set video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID); +} + +void +MediaDecoderStateMachine::CancelMediaDecoderReaderWrapperCallback() +{ + DECODER_LOG("MDSM cancel audio callbacks: mVideoCallbackID = %d\n", (int)mAudioCallbackID); + mReader->CancelAudioCallback(mAudioCallbackID); + + DECODER_LOG("MDSM cancel video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID); + mReader->CancelVideoCallback(mVideoCallbackID); +} + void MediaDecoderStateMachine::StopPlayback() { MOZ_ASSERT(OnTaskQueue()); DECODER_LOG("StopPlayback()"); mOnPlaybackEvent.Notify(MediaEventType::PlaybackStopped); if (IsPlaying()) { @@ -1144,20 +1167,17 @@ MediaDecoderStateMachine::SetDormant(boo SeekTarget::Accurate, MediaDecoderEventVisibility::Suppressed); // XXXbholley - Nobody is listening to this promise. Do we need to pass it // back to MediaDecoder when we come out of dormant? RefPtr<MediaDecoder::SeekPromise> unused = mQueuedSeek.mPromise.Ensure(__func__); } // Discard the current seek task. - if (mSeekTask) { - mSeekTask->Discard(); - mSeekTask = nullptr; - } + DiscardSeekTaskIfExist(); SetState(DECODER_STATE_DORMANT); if (IsPlaying()) { StopPlayback(); } Reset(); @@ -1184,29 +1204,30 @@ MediaDecoderStateMachine::Shutdown() // Change state before issuing shutdown request to threads so those // threads can start exiting cleanly during the Shutdown call. ScheduleStateMachine(); SetState(DECODER_STATE_SHUTDOWN); mBufferedUpdateRequest.DisconnectIfExists(); mQueuedSeek.RejectIfExists(__func__); - if (mSeekTask) { - mSeekTask->Discard(); - mSeekTask = nullptr; - } + + DiscardSeekTaskIfExist(); #ifdef MOZ_EME mCDMProxyPromise.DisconnectIfExists(); #endif if (IsPlaying()) { StopPlayback(); } + // To break the cycle-reference between MediaDecoderReaderWrapper and MDSM. + CancelMediaDecoderReaderWrapperCallback(); + Reset(); mMediaSink->Shutdown(); DECODER_LOG("Shutdown started"); // Put a task in the decode queue to shutdown the reader. // the queue to spin down. @@ -1452,22 +1473,23 @@ MediaDecoderStateMachine::DispatchDecode } void MediaDecoderStateMachine::InitiateSeek(SeekJob aSeekJob) { MOZ_ASSERT(OnTaskQueue()); // Discard the existing seek task. - if (mSeekTask) { - mSeekTask->Discard(); - } + DiscardSeekTaskIfExist(); mSeekTaskRequest.DisconnectIfExists(); + // SeekTask will register its callbacks to MediaDecoderReaderWrapper. + CancelMediaDecoderReaderWrapperCallback(); + // Create a new SeekTask instance for the incoming seek task. mSeekTask = SeekTask::CreateSeekTask(mDecoderID, OwnerThread(), mReader.get(), Move(aSeekJob), mInfo, Duration(), GetMediaTime()); // Stop playback now to ensure that while we're outside the monitor // dispatching SeekingStarted, playback doesn't advance and mess with // mCurrentPosition that we've setting to seekTime here. @@ -1520,19 +1542,16 @@ MediaDecoderStateMachine::OnSeekTaskReso StopPrerollingAudio(); } if (aValue.mNeedToStopPrerollingVideo) { StopPrerollingVideo(); } SeekCompleted(); - - mSeekTask->Discard(); - mSeekTask = nullptr; } void MediaDecoderStateMachine::OnSeekTaskRejected(SeekTaskRejectValue aValue) { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState == DECODER_STATE_SEEKING); @@ -1553,18 +1572,29 @@ MediaDecoderStateMachine::OnSeekTaskReje } if (aValue.mNeedToStopPrerollingVideo) { StopPrerollingVideo(); } DecodeError(); - mSeekTask->Discard(); - mSeekTask = nullptr; + DiscardSeekTaskIfExist(); +} + +void +MediaDecoderStateMachine::DiscardSeekTaskIfExist() +{ + if (mSeekTask) { + mSeekTask->Discard(); + mSeekTask = nullptr; + + // Reset the MediaDecoderReaderWrapper's callbask. + SetMediaDecoderReaderWrapperCallback(); + } } nsresult MediaDecoderStateMachine::DispatchAudioDecodeTaskIfNeeded() { MOZ_ASSERT(OnTaskQueue()); if (IsShutdown()) { @@ -1587,17 +1617,17 @@ MediaDecoderStateMachine::EnsureAudioDec SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d status=%s", IsAudioDecoding(), AudioRequestStatus()); if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) { return NS_OK; } - if (!IsAudioDecoding() || mAudioDataRequest.Exists() || + if (!IsAudioDecoding() || mReader->IsRequestingAudioData() || mAudioWaitRequest.Exists()) { return NS_OK; } RequestAudioData(); return NS_OK; } @@ -1605,21 +1635,17 @@ void MediaDecoderStateMachine::RequestAudioData() { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState != DECODER_STATE_SEEKING); SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o", AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames()); - mAudioDataRequest.Begin( - mReader->RequestAudioData() - ->Then(OwnerThread(), __func__, this, - &MediaDecoderStateMachine::OnAudioDecoded, - &MediaDecoderStateMachine::OnAudioNotDecoded)); + mReader->RequestAudioData(); } nsresult MediaDecoderStateMachine::DispatchVideoDecodeTaskIfNeeded() { MOZ_ASSERT(OnTaskQueue()); if (IsShutdown()) { @@ -1642,55 +1668,45 @@ MediaDecoderStateMachine::EnsureVideoDec SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s", IsVideoDecoding(), VideoRequestStatus()); if (mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) { return NS_OK; } - if (!IsVideoDecoding() || mVideoDataRequest.Exists() || + if (!IsVideoDecoding() || mReader->IsRequestingVidoeData() || mVideoWaitRequest.Exists()) { return NS_OK; } RequestVideoData(); return NS_OK; } void MediaDecoderStateMachine::RequestVideoData() { MOZ_ASSERT(OnTaskQueue()); MOZ_ASSERT(mState != DECODER_STATE_SEEKING); - // Time the video decode, so that if it's slow, we can increase our low - // audio threshold to reduce the chance of an audio underrun while we're - // waiting for a video decode to complete. - TimeStamp videoDecodeStartTime = TimeStamp::Now(); - bool skipToNextKeyFrame = mSentFirstFrameLoadedEvent && NeedToSkipToNextKeyframe(); media::TimeUnit currentTime = media::TimeUnit::FromMicroseconds(GetMediaTime()); SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld", VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame, currentTime.ToMicroseconds()); - RefPtr<MediaDecoderStateMachine> self = this; - mVideoDataRequest.Begin( - mReader->RequestVideoData(skipToNextKeyFrame, currentTime) - ->Then(OwnerThread(), __func__, - [self, videoDecodeStartTime] (MediaData* aVideoSample) { - self->OnVideoDecoded(aVideoSample, videoDecodeStartTime); - }, - [self] (MediaDecoderReader::NotDecodedReason aReason) { - self->OnVideoNotDecoded(aReason); - })); + // MediaDecoderReaderWrapper::RequestVideoData() records the decoding start + // time and sent it back to MDSM::OnVideoDecoded() so that if the decoding is + // slow, we can increase our low audio threshold to reduce the chance of an + // audio underrun while we're waiting for a video decode to complete. + mReader->RequestVideoData(skipToNextKeyFrame, currentTime); } void MediaDecoderStateMachine::StartMediaSink() { MOZ_ASSERT(OnTaskQueue()); if (!mMediaSink->IsStarted()) { mAudioCompleted = false; @@ -2001,16 +2017,25 @@ MediaDecoderStateMachine::SeekCompleted( DECODER_LOG("Changed state from SEEKING (to %lld) to DECODING", seekTime); nextState = DECODER_STATE_DECODING; } // We want to resolve the seek request prior finishing the first frame // to ensure that the seeked event is fired prior loadeded. mSeekTask->GetSeekJob().Resolve(nextState == DECODER_STATE_COMPLETED, __func__); + // Discard and nullify the seek task. + // Reset the MediaDecoderReaderWrapper's callbask. + DiscardSeekTaskIfExist(); + + // NOTE: Discarding the mSeekTask must be done before here. The following code + // might ask the MediaDecoderReaderWrapper to request media data, however, the + // SeekTask::Discard() will ask MediaDecoderReaderWrapper to discard media + // data requests. + if (mDecodingFirstFrame) { // We were resuming from dormant, or initiated a seek early. // We can fire loadeddata now. FinishDecodeFirstFrame(); } if (nextState == DECODER_STATE_DECODING) { StartDecoding(); @@ -2153,18 +2178,18 @@ nsresult MediaDecoderStateMachine::RunSt (mQuickBuffering ? "(quick exit)" : "")); ScheduleStateMachineIn(USECS_PER_S); return NS_OK; } } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) { MOZ_ASSERT(mReader->IsWaitForDataSupported(), "Don't yet have a strategy for non-heuristic + non-WaitForData"); DispatchDecodeTasksIfNeeded(); - MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioDataRequest.Exists() || mAudioWaitRequest.Exists()); - MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoDataRequest.Exists() || mVideoWaitRequest.Exists()); + MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mReader->IsRequestingAudioData() || mAudioWaitRequest.Exists()); + MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mReader->IsRequestingVidoeData() || mVideoWaitRequest.Exists()); DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, " "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s", OutOfDecodedAudio(), AudioRequestStatus(), OutOfDecodedVideo(), VideoRequestStatus()); return NS_OK; } DECODER_LOG("Changed state from BUFFERING to DECODING"); @@ -2248,19 +2273,17 @@ MediaDecoderStateMachine::Reset() mDecodedVideoEndTime = 0; mDecodedAudioEndTime = 0; mAudioCompleted = false; mVideoCompleted = false; AudioQueue().Reset(); VideoQueue().Reset(); mMetadataRequest.DisconnectIfExists(); - mAudioDataRequest.DisconnectIfExists(); mAudioWaitRequest.DisconnectIfExists(); - mVideoDataRequest.DisconnectIfExists(); mVideoWaitRequest.DisconnectIfExists(); mSeekTaskRequest.DisconnectIfExists(); mPlaybackOffset = 0; mReader->ResetDecode(); } @@ -2719,16 +2742,42 @@ MediaDecoderStateMachine::CanonicalBuffe } MediaEventSource<void>& MediaDecoderStateMachine::OnMediaNotSeekable() const { return mReader->OnMediaNotSeekable(); } +const char* +MediaDecoderStateMachine::AudioRequestStatus() const +{ + MOZ_ASSERT(OnTaskQueue()); + if (mReader->IsRequestingAudioData()) { + MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists()); + return "pending"; + } else if (mAudioWaitRequest.Exists()) { + return "waiting"; + } + return "idle"; +} + +const char* +MediaDecoderStateMachine::VideoRequestStatus() const +{ + MOZ_ASSERT(OnTaskQueue()); + if (mReader->IsRequestingVidoeData()) { + MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists()); + return "pending"; + } else if (mVideoWaitRequest.Exists()) { + return "waiting"; + } + return "idle"; +} + } // namespace mozilla // avoid redefined macro in unified build #undef LOG #undef DECODER_LOG #undef VERBOSE_LOG #undef DECODER_WARN #undef DECODER_WARN_HELPER
--- a/dom/media/MediaDecoderStateMachine.h +++ b/dom/media/MediaDecoderStateMachine.h @@ -82,16 +82,17 @@ hardware (via AudioStream). #if !defined(MediaDecoderStateMachine_h__) #define MediaDecoderStateMachine_h__ #include "mozilla/Attributes.h" #include "mozilla/ReentrantMonitor.h" #include "mozilla/StateMirroring.h" #include "nsThreadUtils.h" +#include "MediaCallbackID.h" #include "MediaDecoder.h" #include "MediaDecoderReader.h" #include "MediaDecoderOwner.h" #include "MediaEventSource.h" #include "MediaMetadataManager.h" #include "MediaStatistics.h" #include "MediaTimer.h" #include "ImageContainer.h" @@ -138,16 +139,19 @@ public: typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus; typedef mozilla::layers::ImageContainer::FrameID FrameID; MediaDecoderStateMachine(MediaDecoder* aDecoder, MediaDecoderReader* aReader, bool aRealTime = false); nsresult Init(MediaDecoder* aDecoder); + void SetMediaDecoderReaderWrapperCallback(); + void CancelMediaDecoderReaderWrapperCallback(); + // Enumeration for the valid decoding states enum State { DECODER_STATE_DECODING_METADATA, DECODER_STATE_WAIT_FOR_CDM, DECODER_STATE_DORMANT, DECODER_STATE_DECODING, DECODER_STATE_SEEKING, DECODER_STATE_BUFFERING, @@ -674,16 +678,20 @@ private: // mSeekTask is responsible for executing the current seek request. RefPtr<SeekTask> mSeekTask; MozPromiseRequestHolder<SeekTask::SeekTaskPromise> mSeekTaskRequest; void OnSeekTaskResolved(SeekTaskResolveValue aValue); void OnSeekTaskRejected(SeekTaskRejectValue aValue); + // This method discards the seek task and then get the ownership of + // MedaiDecoderReaderWarpper back via registering MDSM's callback into it. + void DiscardSeekTaskIfExist(); + // Media Fragment end time in microseconds. Access controlled by decoder monitor. int64_t mFragmentEndTime; // The media sink resource. Used on the state machine thread. RefPtr<media::MediaSink> mMediaSink; const RefPtr<MediaDecoderReaderWrapper> mReader; @@ -802,43 +810,23 @@ private: // playback. The flags below are true when the corresponding stream is // being "prerolled". bool mIsAudioPrerolling; bool mIsVideoPrerolling; // Only one of a given pair of ({Audio,Video}DataPromise, WaitForDataPromise) // should exist at any given moment. - MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mAudioDataRequest; + CallbackID mAudioCallbackID; MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest; - const char* AudioRequestStatus() - { - MOZ_ASSERT(OnTaskQueue()); - if (mAudioDataRequest.Exists()) { - MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists()); - return "pending"; - } else if (mAudioWaitRequest.Exists()) { - return "waiting"; - } - return "idle"; - } + const char* AudioRequestStatus() const; + CallbackID mVideoCallbackID; MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mVideoWaitRequest; - MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mVideoDataRequest; - const char* VideoRequestStatus() - { - MOZ_ASSERT(OnTaskQueue()); - if (mVideoDataRequest.Exists()) { - MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists()); - return "pending"; - } else if (mVideoWaitRequest.Exists()) { - return "waiting"; - } - return "idle"; - } + const char* VideoRequestStatus() const; MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise>& WaitRequestRef(MediaData::Type aType) { MOZ_ASSERT(OnTaskQueue()); return aType == MediaData::AUDIO_DATA ? mAudioWaitRequest : mVideoWaitRequest; } // True if we shouldn't play our audio (but still write it to any capturing
--- a/dom/media/MediaFormatReader.cpp +++ b/dom/media/MediaFormatReader.cpp @@ -515,17 +515,20 @@ MediaFormatReader::RequestVideoData(bool } if (mShutdown) { NS_WARNING("RequestVideoData on shutdown MediaFormatReader!"); return MediaDataPromise::CreateAndReject(CANCELED, __func__); } media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)}; - if (ShouldSkip(aSkipToNextKeyframe, timeThreshold)) { + // Ensure we have no pending seek going as ShouldSkip could return out of date + // information. + if (!mVideo.HasInternalSeekPending() && + ShouldSkip(aSkipToNextKeyframe, timeThreshold)) { // Cancel any pending demux request. mVideo.mDemuxRequest.DisconnectIfExists(); // I think it's still possible for an output to have been sent from the decoder // and is currently sitting in our event queue waiting to be processed. The following // flush won't clear it, and when we return to the event loop it'll be added to our // output queue and be used. // This code will count that as dropped, which was the intent, but not quite true. @@ -742,16 +745,17 @@ MediaFormatReader::NeedInput(DecoderData // decoded sample. To account for H.264 streams which may require a longer // run of input than we input, decoders fire an "input exhausted" callback, // which overrides our "few more samples" threshold. return !aDecoder.mDraining && !aDecoder.mError && aDecoder.mDecodingRequested && !aDecoder.mDemuxRequest.Exists() && + !aDecoder.mSeekRequest.Exists() && aDecoder.mOutput.Length() <= aDecoder.mDecodeAhead && (aDecoder.mInputExhausted || !aDecoder.mQueuedSamples.IsEmpty() || aDecoder.mTimeThreshold.isSome() || aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput <= aDecoder.mDecodeAhead); } void MediaFormatReader::ScheduleUpdate(TrackType aTrack) @@ -779,16 +783,31 @@ MediaFormatReader::UpdateReceivedNewData if (!decoder.mReceivedNewData) { return false; } // Update our cached TimeRange. decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered(); + // We do not want to clear mWaitingForData while there are pending + // demuxing or seeking operations that could affect the value of this flag. + // This is in order to ensure that we will retry once they complete as we may + // now have new data that could potentially allow those operations to + // successfully complete if tried again. + if (decoder.mSeekRequest.Exists()) { + // Nothing more to do until this operation complete. + return true; + } + if (decoder.mDemuxRequest.Exists()) { + // We may have pending operations to process, so we want to continue + // after UpdateReceivedNewData returns. + return false; + } + if (decoder.mDrainComplete || decoder.mDraining) { // We do not want to clear mWaitingForData or mDemuxEOS while // a drain is in progress in order to properly complete the operation. return false; } bool hasLastEnd; media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd); @@ -804,20 +823,29 @@ MediaFormatReader::UpdateReceivedNewData if (decoder.mTimeThreshold) { decoder.mTimeThreshold.ref().mWaiting = false; } decoder.mWaitingForData = false; if (decoder.mError) { return false; } - if (decoder.HasWaitingPromise()) { - MOZ_ASSERT(!decoder.HasPromise()); - LOG("We have new data. Resolving WaitingPromise"); - decoder.mWaitingPromise.Resolve(decoder.mType, __func__); + + bool hasPendingSeek = + decoder.mTimeThreshold && !decoder.mTimeThreshold.ref().mHasSeeked; + if (hasPendingSeek || decoder.HasWaitingPromise()) { + if (hasPendingSeek) { + LOG("Attempting Internal Seek"); + InternalSeek(aTrack, decoder.mTimeThreshold.ref()); + } + if (decoder.HasWaitingPromise()) { + MOZ_ASSERT(!decoder.HasPromise()); + LOG("We have new data. Resolving WaitingPromise"); + decoder.mWaitingPromise.Resolve(decoder.mType, __func__); + } return true; } if (!mSeekPromise.IsEmpty()) { MOZ_ASSERT(!decoder.HasPromise()); if (mVideo.mSeekRequest.Exists() || mAudio.mSeekRequest.Exists()) { // Already waiting for a seek to complete. Nothing more to do. return true; } @@ -968,45 +996,52 @@ MediaFormatReader::HandleDemuxedSamples( // We have serviced the decoder's request for more data. decoder.mInputExhausted = false; } void MediaFormatReader::InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget) { MOZ_ASSERT(OnTaskQueue()); + LOG("%s internal seek to %f", + TrackTypeToStr(aTrack), aTarget.mTime.ToSeconds()); + auto& decoder = GetDecoderData(aTrack); decoder.mTimeThreshold = Some(aTarget); RefPtr<MediaFormatReader> self = this; decoder.ResetDemuxer(); decoder.mSeekRequest.Begin(decoder.mTrackDemuxer->Seek(decoder.mTimeThreshold.ref().mTime) ->Then(OwnerThread(), __func__, [self, aTrack] (media::TimeUnit aTime) { auto& decoder = self->GetDecoderData(aTrack); decoder.mSeekRequest.Complete(); + MOZ_ASSERT(decoder.mTimeThreshold); + decoder.mTimeThreshold.ref().mHasSeeked = true; self->NotifyDecodingRequested(aTrack); }, [self, aTrack] (DemuxerFailureReason aResult) { auto& decoder = self->GetDecoderData(aTrack); decoder.mSeekRequest.Complete(); switch (aResult) { case DemuxerFailureReason::WAITING_FOR_DATA: self->NotifyWaitingForData(aTrack); break; case DemuxerFailureReason::END_OF_STREAM: + decoder.mTimeThreshold.reset(); self->NotifyEndOfStream(aTrack); break; case DemuxerFailureReason::CANCELED: case DemuxerFailureReason::SHUTDOWN: + decoder.mTimeThreshold.reset(); break; default: + decoder.mTimeThreshold.reset(); self->NotifyError(aTrack); break; } - decoder.mTimeThreshold.reset(); })); } void MediaFormatReader::DrainDecoder(TrackType aTrack) { MOZ_ASSERT(OnTaskQueue()); @@ -1119,17 +1154,18 @@ MediaFormatReader::Update(TrackType aTra if (!decoder.mReceivedNewData) { LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack)); decoder.RejectPromise(WAITING_FOR_DATA, __func__); } } // Now that draining has completed, we check if we have received // new data again as the result may now be different from the earlier // run. - if (UpdateReceivedNewData(aTrack)) { + if (UpdateReceivedNewData(aTrack) || + (decoder.mTimeThreshold && decoder.mSeekRequest.Exists())) { LOGV("Nothing more to do"); return; } } } if (decoder.mNeedDraining) { DrainDecoder(aTrack); @@ -1240,18 +1276,16 @@ MediaFormatReader::WaitForData(MediaData } nsresult MediaFormatReader::ResetDecode() { MOZ_ASSERT(OnTaskQueue()); LOGV(""); - mAudio.mSeekRequest.DisconnectIfExists(); - mVideo.mSeekRequest.DisconnectIfExists(); mSeekPromise.RejectIfExists(NS_OK, __func__); mSkipRequest.DisconnectIfExists(); // Do the same for any data wait promises. mAudio.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::AUDIO_DATA, WaitForDataRejectValue::CANCELED), __func__); mVideo.mWaitingPromise.RejectIfExists(WaitForDataRejectValue(MediaData::VIDEO_DATA, WaitForDataRejectValue::CANCELED), __func__); // Reset miscellaneous seeking state.
--- a/dom/media/MediaFormatReader.h +++ b/dom/media/MediaFormatReader.h @@ -132,22 +132,25 @@ private: bool DecodeDemuxedSamples(TrackType aTrack, MediaRawData* aSample); struct InternalSeekTarget { InternalSeekTarget(const media::TimeUnit& aTime, bool aDropTarget) : mTime(aTime) , mDropTarget(aDropTarget) , mWaiting(false) + , mHasSeeked(false) {} media::TimeUnit mTime; bool mDropTarget; bool mWaiting; + bool mHasSeeked; }; + // Perform an internal seek to aTime. If aDropTarget is true then // the first sample past the target will be dropped. void InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget); // Drain the current decoder. void DrainDecoder(TrackType aTrack); void NotifyNewOutput(TrackType aTrack, MediaData* aSample); void NotifyInputExhausted(TrackType aTrack); @@ -322,16 +325,17 @@ private: virtual bool HasPromise() = 0; virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason, const char* aMethodName) = 0; void ResetDemuxer() { // Clear demuxer related data. mDemuxRequest.DisconnectIfExists(); + mSeekRequest.DisconnectIfExists(); mTrackDemuxer->Reset(); } void ResetState() { MOZ_ASSERT(mOwner->OnTaskQueue()); mDemuxEOS = false; mWaitingForData = false; @@ -348,16 +352,21 @@ private: mLastSampleTime.reset(); mOutput.Clear(); mNumSamplesInput = 0; mNumSamplesOutput = 0; mSizeOfQueue = 0; mNextStreamSourceID.reset(); } + bool HasInternalSeekPending() const + { + return mTimeThreshold && !mTimeThreshold.ref().mHasSeeked; + } + // Used by the MDSM for logging purposes. Atomic<size_t> mSizeOfQueue; // Used by the MDSM to determine if video decoding is hardware accelerated. // This value is updated after a frame is successfully decoded. Atomic<bool> mIsHardwareAccelerated; // Sample format monitoring. uint32_t mLastStreamSourceID; Maybe<uint32_t> mNextStreamSourceID;
--- a/dom/media/SeekTask.cpp +++ b/dom/media/SeekTask.cpp @@ -81,16 +81,19 @@ SeekTask::SeekTask(const void* aDecoderI seekTime = std::min(seekTime, end); seekTime = std::max(int64_t(0), seekTime); NS_ASSERTION(seekTime >= 0 && seekTime <= end, "Can only seek in range [0,duration]"); mSeekJob.mTarget.SetTime(media::TimeUnit::FromMicroseconds(seekTime)); mDropAudioUntilNextDiscontinuity = HasAudio(); mDropVideoUntilNextDiscontinuity = HasVideo(); + + // Configure MediaDecoderReaderWrapper. + SetMediaDecoderReaderWrapperCallback(); } SeekTask::~SeekTask() { MOZ_ASSERT(mIsDiscarded); } void @@ -147,22 +150,21 @@ void SeekTask::Discard() { // Disconnect MediaDecoder. mSeekJob.RejectIfExists(__func__); // Disconnect MDSM. RejectIfExist(__func__); - // Disconnect MediaDecoderReader. + // Disconnect MediaDecoderReaderWrapper. mSeekRequest.DisconnectIfExists(); - mAudioDataRequest.DisconnectIfExists(); - mVideoDataRequest.DisconnectIfExists(); mAudioWaitRequest.DisconnectIfExists(); mVideoWaitRequest.DisconnectIfExists(); + CancelMediaDecoderReaderWrapperCallback(); mIsDiscarded = true; } bool SeekTask::NeedToResetMDSM() const { return true; @@ -211,17 +213,17 @@ nsresult SeekTask::EnsureAudioDecodeTaskQueued() { AssertOwnerThread(); SAMPLE_LOG("EnsureAudioDecodeTaskQueued isDecoding=%d status=%s", IsAudioDecoding(), AudioRequestStatus()); if (!IsAudioDecoding() || - mAudioDataRequest.Exists() || + mReader->IsRequestingAudioData() || mAudioWaitRequest.Exists() || mSeekRequest.Exists()) { return NS_OK; } RequestAudioData(); return NS_OK; } @@ -230,81 +232,76 @@ nsresult SeekTask::EnsureVideoDecodeTaskQueued() { AssertOwnerThread(); SAMPLE_LOG("EnsureVideoDecodeTaskQueued isDecoding=%d status=%s", IsVideoDecoding(), VideoRequestStatus()); if (!IsVideoDecoding() || - mVideoDataRequest.Exists() || + mReader->IsRequestingVidoeData() || mVideoWaitRequest.Exists() || mSeekRequest.Exists()) { return NS_OK; } RequestVideoData(); return NS_OK; } const char* SeekTask::AudioRequestStatus() { AssertOwnerThread(); - if (mAudioDataRequest.Exists()) { + if (mReader->IsRequestingAudioData()) { MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists()); return "pending"; } else if (mAudioWaitRequest.Exists()) { return "waiting"; } return "idle"; } const char* SeekTask::VideoRequestStatus() { AssertOwnerThread(); - if (mVideoDataRequest.Exists()) { + if (mReader->IsRequestingVidoeData()) { MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists()); return "pending"; } else if (mVideoWaitRequest.Exists()) { return "waiting"; } return "idle"; } void SeekTask::RequestAudioData() { AssertOwnerThread(); SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o", !!mSeekedAudioData, mReader->SizeOfAudioQueueInFrames()); - mAudioDataRequest.Begin(mReader->RequestAudioData() - ->Then(OwnerThread(), __func__, this, - &SeekTask::OnAudioDecoded, &SeekTask::OnAudioNotDecoded)); + mReader->RequestAudioData(); } void SeekTask::RequestVideoData() { AssertOwnerThread(); //These two variables are not used in the SEEKING state. const bool skipToNextKeyFrame = false; const media::TimeUnit currentTime = media::TimeUnit::FromMicroseconds(0); SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld", !!mSeekedVideoData, mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame, currentTime.ToMicroseconds()); - mVideoDataRequest.Begin( - mReader->RequestVideoData(skipToNextKeyFrame, currentTime) - ->Then(OwnerThread(), __func__, this, - &SeekTask::OnVideoDecoded, &SeekTask::OnVideoNotDecoded)); + mReader->RequestVideoData(skipToNextKeyFrame, currentTime); } nsresult SeekTask::DropAudioUpToSeekTarget(MediaData* aSample) { AssertOwnerThread(); RefPtr<AudioData> audio(aSample->As<AudioData>()); MOZ_ASSERT(audio && mSeekJob.Exists() && mSeekJob.mTarget.IsAccurate()); @@ -495,17 +492,16 @@ SeekTask::OnSeekRejected(nsresult aResul } void SeekTask::OnAudioDecoded(MediaData* aAudioSample) { AssertOwnerThread(); RefPtr<MediaData> audio(aAudioSample); MOZ_ASSERT(audio); - mAudioDataRequest.Complete(); // The MDSM::mDecodedAudioEndTime will be updated once the whole SeekTask is // resolved. SAMPLE_LOG("OnAudioDecoded [%lld,%lld] disc=%d", (audio ? audio->mTime : -1), (audio ? audio->GetEndTime() : -1), (audio ? audio->mDiscontinuity : 0)); @@ -548,17 +544,16 @@ SeekTask::OnAudioDecoded(MediaData* aAud CheckIfSeekComplete(); } void SeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { AssertOwnerThread(); SAMPLE_LOG("OnAduioNotDecoded (aReason=%u)", aReason); - mAudioDataRequest.Complete(); if (aReason == MediaDecoderReader::DECODE_ERROR) { // If this is a decode error, delegate to the generic error path. RejectIfExist(__func__); return; } // If the decoder is waiting for data, we tell it to call us back when the @@ -597,17 +592,16 @@ SeekTask::OnAudioNotDecoded(MediaDecoder } void SeekTask::OnVideoDecoded(MediaData* aVideoSample) { AssertOwnerThread(); RefPtr<MediaData> video(aVideoSample); MOZ_ASSERT(video); - mVideoDataRequest.Complete(); // The MDSM::mDecodedVideoEndTime will be updated once the whole SeekTask is // resolved. SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d", (video ? video->mTime : -1), (video ? video->GetEndTime() : -1), (video ? video->mDiscontinuity : 0)); @@ -652,17 +646,16 @@ SeekTask::OnVideoDecoded(MediaData* aVid CheckIfSeekComplete(); } void SeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason) { AssertOwnerThread(); SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason); - mVideoDataRequest.Complete(); if (aReason == MediaDecoderReader::DECODE_ERROR) { // If this is a decode error, delegate to the generic error path. RejectIfExist(__func__); return; } // If the decoder is waiting for data, we tell it to call us back when the @@ -705,9 +698,33 @@ SeekTask::OnVideoNotDecoded(MediaDecoder } mIsVideoQueueFinished = true; mDropVideoUntilNextDiscontinuity = false; // To make IsVideoSeekComplete() return TRUE. CheckIfSeekComplete(); } } +void +SeekTask::SetMediaDecoderReaderWrapperCallback() +{ + mAudioCallbackID = + mReader->SetAudioCallback(this, &SeekTask::OnAudioDecoded, + &SeekTask::OnAudioNotDecoded); + + mVideoCallbackID = + mReader->SetVideoCallback(this, &SeekTask::OnVideoDecoded, + &SeekTask::OnVideoNotDecoded); + + DECODER_LOG("SeekTask set audio callbacks: mAudioCallbackID = %d\n", (int)mAudioCallbackID); + DECODER_LOG("SeekTask set video callbacks: mVideoCallbackID = %d\n", (int)mAudioCallbackID); +} + +void +SeekTask::CancelMediaDecoderReaderWrapperCallback() +{ + DECODER_LOG("SeekTask cancel audio callbacks: mVideoCallbackID = %d\n", (int)mAudioCallbackID); + mReader->CancelAudioCallback(mAudioCallbackID); + + DECODER_LOG("SeekTask cancel video callbacks: mVideoCallbackID = %d\n", (int)mVideoCallbackID); + mReader->CancelVideoCallback(mVideoCallbackID); +} } // namespace mozilla
--- a/dom/media/SeekTask.h +++ b/dom/media/SeekTask.h @@ -3,16 +3,17 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SEEK_TASK_H #define SEEK_TASK_H #include "mozilla/MozPromise.h" +#include "MediaCallbackID.h" #include "MediaDecoderReader.h" #include "SeekJob.h" namespace mozilla { class AbstractThread; class MediaData; class MediaDecoderReaderWrapper; @@ -122,16 +123,20 @@ protected: virtual void OnAudioDecoded(MediaData* aAudioSample); virtual void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason); virtual void OnVideoDecoded(MediaData* aVideoSample); virtual void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason); + void SetMediaDecoderReaderWrapperCallback(); + + void CancelMediaDecoderReaderWrapperCallback(); + /* * Data shared with MDSM. */ const void* mDecoderID; // For logging. const RefPtr<AbstractThread> mOwnerThread; const RefPtr<MediaDecoderReaderWrapper> mReader; /* @@ -152,18 +157,18 @@ protected: // the seek target, we will still have a frame that we can display as the // last frame in the media. RefPtr<MediaData> mFirstVideoFrameAfterSeek; /* * Track the current seek promise made by the reader. */ MozPromiseRequestHolder<MediaDecoderReader::SeekPromise> mSeekRequest; - MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mAudioDataRequest; - MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mVideoDataRequest; + CallbackID mAudioCallbackID; + CallbackID mVideoCallbackID; MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest; MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mVideoWaitRequest; /* * Information which are going to be returned to MDSM. */ RefPtr<MediaData> mSeekedAudioData; RefPtr<MediaData> mSeekedVideoData;
--- a/dom/media/gmp/GMPChild.cpp +++ b/dom/media/gmp/GMPChild.cpp @@ -210,33 +210,33 @@ GetAppPaths(nsCString &aAppPath, nsCStri // soft links. aAppPath = GetNativeTarget(app); appBinaryPath = GetNativeTarget(appBinary); return true; } bool -GMPChild::SetMacSandboxInfo() +GMPChild::SetMacSandboxInfo(MacSandboxPluginType aPluginType) { if (!mGMPLoader) { return false; } nsAutoCString pluginDirectoryPath, pluginFilePath; if (!GetPluginPaths(mPluginPath, pluginDirectoryPath, pluginFilePath)) { return false; } nsAutoCString appPath, appBinaryPath; if (!GetAppPaths(appPath, appBinaryPath)) { return false; } MacSandboxInfo info; info.type = MacSandboxType_Plugin; - info.pluginInfo.type = MacSandboxPluginType_GMPlugin_Default; + info.pluginInfo.type = aPluginType; info.pluginInfo.pluginPath.assign(pluginDirectoryPath.get()); info.pluginInfo.pluginBinaryPath.assign(pluginFilePath.get()); info.appPath.assign(appPath.get()); info.appBinaryPath.assign(appBinaryPath.get()); mGMPLoader->SetSandboxInfo(&info); return true; } @@ -370,27 +370,37 @@ GMPChild::AnswerStartPlugin(const nsStri mGMPLoader = GMPProcessChild::GetGMPLoader(); if (!mGMPLoader) { NS_WARNING("Failed to get GMPLoader"); delete platformAPI; return false; } +#ifdef MOZ_WIDEVINE_EME + bool isWidevine = aAdapter.EqualsLiteral("widevine"); +#endif + #if defined(MOZ_GMP_SANDBOX) && defined(XP_MACOSX) - if (!SetMacSandboxInfo()) { + MacSandboxPluginType pluginType = MacSandboxPluginType_GMPlugin_Default; +#ifdef MOZ_WIDEVINE_EME + if (isWidevine) { + pluginType = MacSandboxPluginType_GMPlugin_EME_Widevine; + } +#endif + if (!SetMacSandboxInfo(pluginType)) { NS_WARNING("Failed to set Mac GMP sandbox info"); delete platformAPI; return false; } #endif GMPAdapter* adapter = nullptr; #ifdef MOZ_WIDEVINE_EME - if (aAdapter.EqualsLiteral("widevine")) { + if (isWidevine) { adapter = new WidevineAdapter(); } #endif if (!mGMPLoader->Load(libPath.get(), libPath.Length(), mNodeId.BeginWriting(), mNodeId.Length(), platformAPI,
--- a/dom/media/gmp/GMPChild.h +++ b/dom/media/gmp/GMPChild.h @@ -36,17 +36,17 @@ public: // Main thread only. GMPTimerChild* GetGMPTimers(); GMPStorageChild* GetGMPStorage(); // GMPAsyncShutdownHost void ShutdownComplete() override; #if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX) - bool SetMacSandboxInfo(); + bool SetMacSandboxInfo(MacSandboxPluginType aPluginType); #endif private: friend class GMPContentChild; bool PreLoadPluginVoucher(); void PreLoadSandboxVoucher();
--- a/dom/media/gmp/GMPParent.cpp +++ b/dom/media/gmp/GMPParent.cpp @@ -570,16 +570,33 @@ GMPParent::SupportsAPI(const nsCString& { for (uint32_t i = 0; i < mCapabilities.Length(); i++) { if (!mCapabilities[i].mAPIName.Equals(aAPI)) { continue; } nsTArray<nsCString>& tags = mCapabilities[i].mAPITags; for (uint32_t j = 0; j < tags.Length(); j++) { if (tags[j].Equals(aTag)) { +#ifdef XP_WIN + // Clearkey on Windows advertises that it can decode in its GMP info + // file, but uses Windows Media Foundation to decode. That's not present + // on Windows XP, and on some Vista, Windows N, and KN variants without + // certain services packs. + if (tags[j].EqualsLiteral("org.w3.clearkey")) { + if (mCapabilities[i].mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER)) { + if (!WMFDecoderModule::HasH264()) { + continue; + } + } else if (mCapabilities[i].mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER)) { + if (!WMFDecoderModule::HasAAC()) { + continue; + } + } + } +#endif return true; } } } return false; } bool @@ -883,35 +900,16 @@ GMPParent::ReadGMPInfoFile(nsIFile* aFil // SSE2 isn't supported. if (cap.mAPITags.Contains(NS_LITERAL_CSTRING("com.adobe.primetime")) && !mozilla::supports_sse2()) { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } #endif // XP_WIN } -#ifdef XP_WIN - // Clearkey on Windows advertises that it can decode in its GMP info - // file, but uses Windows Media Foundation to decode. That's not present - // on Windows XP, and on some Vista, Windows N, and KN variants without - // certain services packs. So don't add the decoding capability to - // gmp-clearkey's GMPParent if it's not going to be able to use WMF to - // decode. - if (cap.mAPIName.EqualsLiteral(GMP_API_VIDEO_DECODER) && - cap.mAPITags.Contains(NS_LITERAL_CSTRING("org.w3.clearkey")) && - !WMFDecoderModule::HasH264()) { - continue; - } - if (cap.mAPIName.EqualsLiteral(GMP_API_AUDIO_DECODER) && - cap.mAPITags.Contains(NS_LITERAL_CSTRING("org.w3.clearkey")) && - !WMFDecoderModule::HasAAC()) { - continue; - } -#endif - mCapabilities.AppendElement(Move(cap)); } if (mCapabilities.IsEmpty()) { return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); } return GenericPromise::CreateAndResolve(true, __func__);
--- a/dom/media/gmp/GMPPlatform.cpp +++ b/dom/media/gmp/GMPPlatform.cpp @@ -19,48 +19,48 @@ static GMPChild* sChild = nullptr; static bool IsOnChildMainThread() { return sMainLoop && sMainLoop == MessageLoop::current(); } // We just need a refcounted wrapper for GMPTask objects. -class Runnable final +class GMPRunnable final { public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Runnable) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPRunnable) - explicit Runnable(GMPTask* aTask) + explicit GMPRunnable(GMPTask* aTask) : mTask(aTask) { MOZ_ASSERT(mTask); } void Run() { mTask->Run(); mTask->Destroy(); mTask = nullptr; } private: - ~Runnable() + ~GMPRunnable() { } GMPTask* mTask; }; -class SyncRunnable final +class GMPSyncRunnable final { public: - NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SyncRunnable) + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPSyncRunnable) - SyncRunnable(GMPTask* aTask, MessageLoop* aMessageLoop) + GMPSyncRunnable(GMPTask* aTask, MessageLoop* aMessageLoop) : mDone(false) , mTask(aTask) , mMessageLoop(aMessageLoop) , mMonitor("GMPSyncRunnable") { MOZ_ASSERT(mTask); MOZ_ASSERT(mMessageLoop); } @@ -68,17 +68,17 @@ public: void Post() { // We assert here for two reasons. // 1) Nobody should be blocking the main thread. // 2) This prevents deadlocks when doing sync calls to main which if the // main thread tries to do a sync call back to the calling thread. MOZ_ASSERT(!IsOnChildMainThread()); - mMessageLoop->PostTask(NewRunnableMethod(this, &SyncRunnable::Run)); + mMessageLoop->PostTask(NewRunnableMethod(this, &GMPSyncRunnable::Run)); MonitorAutoLock lock(mMonitor); while (!mDone) { lock.Wait(); } } void Run() { @@ -86,17 +86,17 @@ public: mTask->Destroy(); mTask = nullptr; MonitorAutoLock lock(mMonitor); mDone = true; lock.Notify(); } private: - ~SyncRunnable() + ~GMPSyncRunnable() { } bool mDone; GMPTask* mTask; MessageLoop* mMessageLoop; Monitor mMonitor; }; @@ -115,30 +115,30 @@ CreateThread(GMPThread** aThread) GMPErr RunOnMainThread(GMPTask* aTask) { if (!aTask || !sMainLoop) { return GMPGenericErr; } - RefPtr<Runnable> r = new Runnable(aTask); - sMainLoop->PostTask(NewRunnableMethod(r.get(), &Runnable::Run)); + RefPtr<GMPRunnable> r = new GMPRunnable(aTask); + sMainLoop->PostTask(NewRunnableMethod(r.get(), &GMPRunnable::Run)); return GMPNoErr; } GMPErr SyncRunOnMainThread(GMPTask* aTask) { if (!aTask || !sMainLoop || IsOnChildMainThread()) { return GMPGenericErr; } - RefPtr<SyncRunnable> r = new SyncRunnable(aTask, sMainLoop); + RefPtr<GMPSyncRunnable> r = new GMPSyncRunnable(aTask, sMainLoop); r->Post(); return GMPNoErr; } GMPErr CreateMutex(GMPMutex** aMutex) @@ -247,19 +247,19 @@ GMPThreadImpl::Post(GMPTask* aTask) if (!mThread.IsRunning()) { bool started = mThread.Start(); if (!started) { NS_WARNING("Unable to start GMPThread!"); return; } } - RefPtr<Runnable> r = new Runnable(aTask); + RefPtr<GMPRunnable> r = new GMPRunnable(aTask); - mThread.message_loop()->PostTask(NewRunnableMethod(r.get(), &Runnable::Run)); + mThread.message_loop()->PostTask(NewRunnableMethod(r.get(), &GMPRunnable::Run)); } void GMPThreadImpl::Join() { { MutexAutoLock lock(mMutex); if (mThread.IsRunning()) {
--- a/dom/media/mediasource/MediaSourceDecoder.cpp +++ b/dom/media/mediasource/MediaSourceDecoder.cpp @@ -263,17 +263,17 @@ MediaSourceDecoder::NextFrameBufferedSta } // Next frame hasn't been decoded yet. // Use the buffered range to consider if we have the next frame available. TimeUnit currentPosition = TimeUnit::FromMicroseconds(CurrentPosition()); TimeInterval interval(currentPosition, currentPosition + media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED), MediaSourceDemuxer::EOS_FUZZ); - return GetBuffered().Contains(interval) + return GetBuffered().Contains(ClampIntervalToEnd(interval)) ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE; } bool MediaSourceDecoder::CanPlayThrough() { MOZ_ASSERT(NS_IsMainThread()); @@ -296,15 +296,25 @@ MediaSourceDecoder::CanPlayThrough() } // If we have data up to the mediasource's duration or 30s ahead, we can // assume that we can play without interruption. TimeUnit timeAhead = std::min(duration, currentPosition + TimeUnit::FromSeconds(30)); TimeInterval interval(currentPosition, timeAhead, MediaSourceDemuxer::EOS_FUZZ); - return GetBuffered().Contains(interval); + return GetBuffered().Contains(ClampIntervalToEnd(interval)); +} + +TimeInterval +MediaSourceDecoder::ClampIntervalToEnd(const TimeInterval& aInterval) +{ + if (!mEnded) { + return aInterval; + } + TimeInterval interval(TimeUnit(), TimeUnit::FromSeconds(GetDuration())); + return aInterval.Intersection(interval); } #undef MSE_DEBUG #undef MSE_DEBUGV } // namespace mozilla
--- a/dom/media/mediasource/MediaSourceDecoder.h +++ b/dom/media/mediasource/MediaSourceDecoder.h @@ -78,16 +78,17 @@ public: void AddSizeOfResources(ResourceSizes* aSizes) override; MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus() override; bool CanPlayThrough() override; private: void DoSetMediaSourceDuration(double aDuration); + media::TimeInterval ClampIntervalToEnd(const media::TimeInterval& aInterval); // The owning MediaSource holds a strong reference to this decoder, and // calls Attach/DetachMediaSource on this decoder to set and clear // mMediaSource. dom::MediaSource* mMediaSource; RefPtr<MediaSourceDemuxer> mDemuxer; RefPtr<MediaFormatReader> mReader;
new file mode 100755 index 0000000000000000000000000000000000000000..3dad336e8e22b5444da55cbf3e874ed72b7628a7 GIT binary patch literal 66806 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 V(S`~!bQIEPhf)3+*Z;4d{|9m#+S>pC
new file mode 100644 --- /dev/null +++ b/dom/media/mediasource/test/bipbop/bipbop_480_624kbps-video1.m4s^headers^ @@ -0,0 +1,1 @@ +Cache-Control: no-store
new file mode 100755 index 0000000000000000000000000000000000000000..dd7491241f89daf088896df8abfb5c345168316f GIT binary patch literal 65292 zc$}2H2V7H4w>LUT=)Fqs9YsJy5P{G^1QAd{K)Q&c6h%QP3B3qXL@6R57NjUxC`w6a ziU@)QQ4kUkMQI5=5R&=gQ{LzK&OP^>d-I!}z4ywR*)#vO*34QndjbFuJs*B4^z3>6 zK*0F?KA$~*Dw1)z<NPWA-{;>S1VDIY1H?YV|J(z54?u{SWdC`)bv7g<fN{ZhHXzXd zFCHX=?j3&4=MQ@LZ#wetyua`L!~eYFk!F19ho8F;3;-uHgCfev%JgRuBB226tzyud z{>;aI=g(`A5CE7S{Zkj#xqs2O{-NuL{EOcG51kAAi*Ea`a%%sfGnfBEXEXj6o&R6_ z2me)0*FSXj5C7r^|KdOPFMig4=qv;O;_vtuf5e}3&PD)GrGJut-!nV@g-POH_zL}n zuk3H!|AhHp7z3N%U#@>*jQov1qYYa#j5EuBIT&?9c>FGpaftmlzyCMKR^M5FpWn|B zQn5FYt`rqwZ2%udnZ!pqhFi&e3_6qQIC~G{EX=~pk4jlN*w1m`>+L*)&W|@mWB?X` zZMT$~WM#b|ocLJ=<PJo%QAz*rY4*{J|1sfcX-qsv_hDJDb2gMctT1>XOP`Ab+km$` zI~EH(ftG;VOR%ly_?)6zA7-)U1ncuJc|14rF=Rpc1g&Fc@6Db-S|Qgf&J-af8Hs0F zO#;@tghicj%YI>5&YRKvM;H}M_cEdToRKwM1{ZavFct!9J*77J2)&sVaTeJb50 zcYW}sS=LV%x-4gy2#IzoRP4R9*k1kM(1JHzT3Ib|rAdc{`mm=j9F)%C6EG97j@ysM zo0Zilob<mE!CGHdyy&DP+&!$x7xg^d;;2iv145)Mp8D~7jAd=^VCTm`TfQ+|J^O{b zERK$3-#lXne%QzhJCcZq02hSAoL1PrCW8y~g$z7%Jgb<)So99sXT6}ZO+=T3KYqVK z`R%^@JQ>5@eCCz<hOKSYy}F-Kg&B|dgJe&BRXwEfBVgBy;t3YaKGpB(-eTp?HB+GL z*{f$+ZtV|z`Mnv=Nw6}1lA3*YKzd1@UyRFNw#O+~3uhu&pmu&%_Iz8(k^;Z0`j^Vk zj^^`O!l<IU>&pP(5g_pzV3&KWzW@Fwq|aRzbc~G-hrHf1v6T07NN-ISzc|tIUeXpY zw2<E=+GCo1Pa)3#u-5h$i{*8%wq&?iXH=yR^<Xb2+?t<a3zv(?;2e&z^X~>I6~}5% z`g>O`=VV)v;GsLTH*Ef06Siy*T?r6M%CbQAd{qA_eCP{8C;uV1g0d4Y&*DkiQok;^ z{?o_iqMi3kn%t|jtQwGgyzr)&hGlHF$pG2S*7->&sJqlIHsT|H{NVs5CjP2cyu5$L z({(ETsP7MKwFs9ZvS?+SPDcH~Qop;yV+gaF&eMmL2p$<-I2R%!LEFzSpO&d8#@e?{ z&f`eC%+g4Pwj<B;YwNSfVB|W>>&U5kFH)_fNn)2`K85eJgKKFAZ@;Y&?S7eemYD08 z%b$3u2vZxrwgd0kf``M`$1Thh<T4V)IGl7~ao9i%aYgLi^|&u=9j9R=_YE%LP9@Gp zTi7-s&*&}qIPLSKOcyiGx$7YfRyMXU>xRpSLzmv?DUD*r4U+p(_fc4cs7J5rgx?4c zdxY@k6v9$Pms14~ykon#-Hh@~x02n2)lNrb-+5M-%Z*!2JQec3rdH2sFS~Rq1v>1T z8)Hba{IV|^MG_1ao8=BI9>;d~K%>keZ6T~#kOxG61?E2ArA?lm4pCr{y7m>v`EtXo z*no}c^FF$OsQ>YqgyXHjaT>ZTqB+h<g%7=<x5f@_?lmoy9{Yu59kJsFLle-kdFPRy zJaQb?`{#v<<^n>sR`1>XhCa6%muNhkc**R{?dGG$cH3#b$r|{	VPKEt%`&dZPc7 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 YO}W)N>wlEoma=Zm`QrZ^r+;z%Ur|t1<NyEw
new file mode 100644 --- /dev/null +++ b/dom/media/mediasource/test/bipbop/bipbop_480_624kbps-video2.m4s^headers^ @@ -0,0 +1,1 @@ +Cache-Control: no-store
new file mode 100755 index 0000000000000000000000000000000000000000..b1a2d44058f1c093c0ba448a721b558a541ce216 GIT binary patch literal 1410 zc$}?L&1(}u6rb6otzU<gt`IemRVppWhNO1YW2An>k3vDrK`UjOnQfNtWS5!AwtCY4 zp#B9OrT>6<5j=SCDhM7#QM`Ec>Z!h+Y{(={f`X5oeeXB#&71e$ZxBL7Up|fl5xEE< z>?O@=Y7<Yi03Y%>rJ!%8`j&`RC<#3KMi5eal}Du0q{QA1WXry}dUe^Zl-=^8bM1P= z;WVjMTxXNeCHEq|>@ZJA$|2E|#iA$yBV<X7paIGKR0^xFFL*F&7}wB5$-&xT3Ecs0 zGMe3adixRlZOPSsFDtC0ueCdLztP>z>$y$v$J%)vL%cC9xwi%On%q+NrnR6CFG{~h zboEdD8c$*Sn}QWeqyW7@Lm{<V*Y`mN&MF&o1`MXXF)}oNyuO>N`|lKTS|sE>R}v7a zhc*C5b+1iAMJg__pzVWUB}^sNubm@ZUy*R?Yo#3P@X*feFG6mFpq~pVh!*oKeU4+s z0z-fi)WN8ai#Q+!7zsibAnP(-sh!#Xj;wjmdj)Ie75Z4npZa{{)0<x=M)`%Whu=e} zDWsZiPXLRihAqHPhAx4aX4HrH?Sxlgj3^T0QO!(#^>f)PC~B{uf1;c4%2?)6<6Qb5 z#k`0*Z9VcHVA5wE{m7U%1sh2EH>|(RSbsRg`W+T8c?X&>kl8O4%~L@XF}aiA$sezs zP}{T3bWItvyVKY}|Ajxa(IGM|=K-U#SJqj5Mle_I#c`-7ae^)p=l5ikCMyw79ajC^ P^%CE`RIa+^YGv^c$=AL=
new file mode 100644 --- /dev/null +++ b/dom/media/mediasource/test/bipbop/bipbop_480_624kbps-videoinit.mp4^headers^ @@ -0,0 +1,1 @@ +Cache-Control: no-store
--- a/dom/media/mediasource/test/mediasource.js +++ b/dom/media/mediasource/test/mediasource.js @@ -110,9 +110,22 @@ function fetchAndLoad(sb, prefix, chunks //Register timeout function to dump debugging logs. SimpleTest.registerTimeoutFunction(function() { for (var v of document.getElementsByTagName("video")) { v.mozDumpDebugInfo(); } for (var a of document.getElementsByTagName("audio")) { a.mozDumpDebugInfo(); } -}); \ No newline at end of file +}); + +function waitUntilTime(target, targetTime) { + return new Promise(function(resolve, reject) { + target.addEventListener("waiting", function onwaiting() { + info("Got a waiting event at " + target.currentTime); + if (target.currentTime >= targetTime) { + ok(true, "Reached target time of: " + targetTime); + target.removeEventListener("waiting", onwaiting); + resolve(); + } + }); + }); +}
--- a/dom/media/mediasource/test/mochitest.ini +++ b/dom/media/mediasource/test/mochitest.ini @@ -35,16 +35,19 @@ support-files = bipbop/bipbop12.m4s^headers^ bipbop/bipbop_video12.m4s^headers^ bipbop/bipbop13.m4s^headers^ bipbop/bipbop_video13.m4s^headers^ aac20-48000-64000-init.mp4 aac20-48000-64000-init.mp4^headers^ aac20-48000-64000-1.m4s aac20-48000-64000-1.m4s^headers^ aac20-48000-64000-2.m4s aac20-48000-64000-2.m4s^headers^ aac51-48000-128000-init.mp4 aac51-48000-128000-init.mp4^headers^ aac51-48000-128000-1.m4s aac51-48000-128000-1.m4s^headers^ aac51-48000-128000-2.m4s aac51-48000-128000-2.m4s^headers^ + bipbop/bipbop_480_624kbps-videoinit.mp4 bipbop/bipbop_480_624kbps-videoinit.mp4^headers^ + bipbop/bipbop_480_624kbps-video1.m4s bipbop/bipbop_480_624kbps-video1.m4s^headers^ + bipbop/bipbop_480_624kbps-video2.m4s bipbop/bipbop_480_624kbps-video2.m4s^headers^ [test_AudioChange_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_BufferedSeek.html] [test_BufferedSeek_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_BufferingWait.html] skip-if = toolkit == 'android' #timeout android bug 1199531 @@ -55,16 +58,18 @@ skip-if = ((os == "win" && os_version == [test_EndOfStream.html] skip-if = (true || toolkit == 'android' || buildapp == 'mulet') #timeout android/mulet only bug 1101187 and bug 1182946 [test_EndOfStream_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android' || buildapp == 'mulet')) # Not supported on xp and android 2.3 [test_DurationUpdated.html] [test_DurationUpdated_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_FrameSelection.html] +[test_FrameSelection_mp4.html] +skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_HaveMetadataUnbufferedSeek.html] [test_HaveMetadataUnbufferedSeek_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_LoadedDataFired_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_LoadedMetadataFired.html] [test_LoadedMetadataFired_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 @@ -73,16 +78,18 @@ skip-if = ((os == "win" && os_version == [test_MediaSource_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_MediaSource_disabled.html] [test_MultipleInitSegments.html] [test_MultipleInitSegments_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_PlayEvents.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 +[test_ResumeAfterClearing_mp4.html] +skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_SeekableAfterEndOfStream.html] [test_SeekableAfterEndOfStream_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_SeekableAfterEndOfStreamSplit.html] [test_SeekableAfterEndOfStreamSplit_mp4.html] skip-if = ((os == "win" && os_version == "5.1") || (toolkit == 'android')) # Not supported on xp and android 2.3 [test_SeekableBeforeEndOfStream.html] [test_SeekableBeforeEndOfStream_mp4.html]
new file mode 100644 --- /dev/null +++ b/dom/media/mediasource/test/test_FrameSelection_mp4.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<html><head> +<meta http-equiv="content-type" content="text/html; charset=windows-1252"> + <title>MSE: Don't get stuck buffering for too long when we have frames to show</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="mediasource.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"><script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +// This test loads partial video, plays and waits until playback stalls. +// It then loads only 3 frames of a video at higher resolution. + +var receivedSourceOpen = false; +runWithMSE(function(ms, v) { + ms.addEventListener("sourceopen", function() { + ok(true, "Receive a sourceopen event"); + ok(!receivedSourceOpen, "Should only receive one sourceopen for this test"); + receivedSourceOpen = true; + var sb = ms.addSourceBuffer("video/mp4"); + ok(sb, "Create a SourceBuffer"); + + // Log events for debugging. + var events = ["suspend", "play", "canplay", "canplaythrough", "loadstart", "loadedmetadata", + "loadeddata", "playing", "ended", "error", "stalled", "emptied", "abort", + "waiting", "pause", "durationchange", "seeking", "seeked"]; + function logEvent(e) { + var v = e.target; + info("got " + e.type + " event"); + } + events.forEach(function(e) { + v.addEventListener(e, logEvent, false); + }); + + sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); }); + fetchAndLoad(sb, 'bipbop/bipbop', ['init'], '.mp4') + .then(function() { + var promises = []; + promises.push(fetchAndLoad(sb, 'bipbop/bipbop', range(1,3), '.m4s')); + promises.push(once(v, "loadeddata")); + return Promise.all(promises); + }).then(function() { + is(sb.buffered.length, 1, "continuous range"); + v.play(); + // We have nothing to play, waiting will be fired. + return waitUntilTime(v, 1.5); + }).then(function() { + return fetchAndLoad(sb, 'bipbop/bipbop_480_624kbps-video', ['init'], '.mp4'); + }).then(function() { + sb.timestampOffset = 1.601666; // End of the video track buffered - time of first video sample (0.095). + sb.appendWindowEnd = 1.796677; // Only allow room for three extra video frames (we need 3 as this video has b-frames). + return fetchAndLoad(sb, 'bipbop/bipbop_480_624kbps-video', ['1'], '.m4s'); + }).then(function() { + ms.endOfStream(); + var promises = []; + promises.push(once(ms, "sourceended")); + promises.push(once(v, "playing")); + promises.push(once(v, "ended")); + return Promise.all(promises); + }).then(function() { + if(v.width, 640, "has proper width"); + if(v.height, 480, "has proper height"); + SimpleTest.finish(); + }); + }); +}); +</script> +</pre> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/dom/media/mediasource/test/test_ResumeAfterClearing_mp4.html @@ -0,0 +1,55 @@ +<!DOCTYPE html> +<html><head> +<meta http-equiv="content-type" content="text/html; charset=windows-1252"> + <title>MSE: Don't get stuck buffering for too long when we have frames to show</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="mediasource.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<pre id="test"><script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +var receivedSourceOpen = false; +runWithMSE(function(ms, v) { + ms.addEventListener("sourceopen", function() { + ok(true, "Receive a sourceopen event"); + ok(!receivedSourceOpen, "Should only receive one sourceopen for this test"); + receivedSourceOpen = true; + var sb = ms.addSourceBuffer("video/mp4"); + ok(sb, "Create a SourceBuffer"); + + sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); }); + fetchAndLoad(sb, 'bipbop/bipbop', ['init'], '.mp4') + .then(function() { + var promises = []; + promises.push(fetchAndLoad(sb, 'bipbop/bipbop', range(1,3), '.m4s')); + promises.push(once(v, "loadeddata")); + return Promise.all(promises); + }).then(function() { + // clear the entire sourcebuffer. + sb.remove(0, 5); + return once(sb, "updateend"); + }).then(function() { + v.play(); + // We have nothing to play, waiting will be fired. + return once(v, "waiting"); + }).then(function() { + var promises = []; + promises.push(once(v, "playing")); + promises.push(fetchAndLoad(sb, 'bipbop/bipbop', range(1,3), '.m4s')); + return Promise.all(promises); + }).then(function() { + ms.endOfStream(); + var promises = []; + promises.push(once(ms, "sourceended")); + promises.push(once(v, "ended")); + return Promise.all(promises); + }).then(SimpleTest.finish.bind(SimpleTest)); + }); +}); +</script> +</pre> +</body> +</html>
--- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -103,16 +103,17 @@ EXPORTS += [ 'DOMMediaStream.h', 'EncodedBufferCache.h', 'FileBlockCache.h', 'FlushableTaskQueue.h', 'FrameStatistics.h', 'Intervals.h', 'Latency.h', 'MediaCache.h', + 'MediaCallbackID.h', 'MediaData.h', 'MediaDataDemuxer.h', 'MediaDecoder.h', 'MediaDecoderOwner.h', 'MediaDecoderReader.h', 'MediaDecoderStateMachine.h', 'MediaEventSource.h', 'MediaFormatReader.h', @@ -213,16 +214,17 @@ UNIFIED_SOURCES += [ 'DOMMediaStream.cpp', 'EncodedBufferCache.cpp', 'FileBlockCache.cpp', 'FlushableTaskQueue.cpp', 'GetUserMediaRequest.cpp', 'GraphDriver.cpp', 'Latency.cpp', 'MediaCache.cpp', + 'MediaCallbackID.cpp', 'MediaData.cpp', 'MediaDecoder.cpp', 'MediaDecoderReader.cpp', 'MediaDecoderReaderWrapper.cpp', 'MediaDecoderStateMachine.cpp', 'MediaDeviceInfo.cpp', 'MediaDevices.cpp', 'MediaFormatReader.cpp',
--- a/dom/media/test/test_decode_error.html +++ b/dom/media/test/test_decode_error.html @@ -7,16 +7,23 @@ <script type="text/javascript" src="manifest.js"></script> </head> <body> <pre id="test"> <script class="testbody" type="text/javascript"> var manager = new MediaTestManager; function startTest(test, token) { + var ok = function (condition, name) { + SimpleTest.ok(condition, test.name + ": " + name); + } + var is = function (a, b, name) { + SimpleTest.is(a, b, test.name + ": " + name); + } + var v = document.createElement("video"); manager.started(token); v.addEventListener("error", function (event) { var el = event.currentTarget; is(event.type, "error", "Expected event of type 'error'"); ok(el.error, "Element 'error' attr expected to have a value"); ok(el.error instanceof MediaError, "Element 'error' attr expected to be MediaError"); is(el.error.code, MediaError.MEDIA_ERR_DECODE, "Expected a decode error");
--- a/dom/moz.build +++ b/dom/moz.build @@ -41,17 +41,16 @@ DIRS += ['interfaces/' + i for i in inte DIRS += [ 'animation', 'apps', 'base', 'bluetooth', 'activities', 'archivereader', - 'requestsync', 'bindings', 'battery', 'browser-element', 'cache', 'canvas', 'cellbroadcast', 'contacts', 'crypto',
--- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -284,17 +284,18 @@ public: }; class GetPermissionRunnable final : public WorkerMainThreadRunnable { NotificationPermission mPermission; public: explicit GetPermissionRunnable(WorkerPrivate* aWorker) - : WorkerMainThreadRunnable(aWorker) + : WorkerMainThreadRunnable(aWorker, + NS_LITERAL_CSTRING("Notification :: Get Permission")) , mPermission(NotificationPermission::Denied) { } bool MainThreadRun() override { ErrorResult result; mPermission = @@ -2443,17 +2444,18 @@ NotificationFeature::NotificationFeature class CloseNotificationRunnable final : public WorkerMainThreadRunnable { Notification* mNotification; bool mHadObserver; public: explicit CloseNotificationRunnable(Notification* aNotification) - : WorkerMainThreadRunnable(aNotification->mWorkerPrivate) + : WorkerMainThreadRunnable(aNotification->mWorkerPrivate, + NS_LITERAL_CSTRING("Notification :: Close Notification")) , mNotification(aNotification) , mHadObserver(false) {} bool MainThreadRun() override { if (mNotification->mObserver) { @@ -2550,17 +2552,18 @@ Notification::UnregisterFeature() */ class CheckLoadRunnable final : public WorkerMainThreadRunnable { nsresult mRv; nsCString mScope; public: explicit CheckLoadRunnable(WorkerPrivate* aWorker, const nsACString& aScope) - : WorkerMainThreadRunnable(aWorker) + : WorkerMainThreadRunnable(aWorker, + NS_LITERAL_CSTRING("Notification :: Check Load")) , mRv(NS_ERROR_DOM_SECURITY_ERR) , mScope(aScope) { } bool MainThreadRun() override { nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp +++ b/dom/plugins/base/nsPluginInstanceOwner.cpp @@ -332,25 +332,24 @@ nsPluginInstanceOwner::UpdateScrollState nsresult rv = mInstance->UpdateScrollState(aIsScrolling); return NS_SUCCEEDED(rv); #else return false; #endif } nsPluginInstanceOwner::nsPluginInstanceOwner() + : mPluginWindow(nullptr) { // create nsPluginNativeWindow object, it is derived from NPWindow // struct and allows to manipulate native window procedure nsCOMPtr<nsIPluginHost> pluginHostCOM = do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); mPluginHost = static_cast<nsPluginHost*>(pluginHostCOM.get()); if (mPluginHost) mPluginHost->NewPluginNativeWindow(&mPluginWindow); - else - mPluginWindow = nullptr; mPluginFrame = nullptr; mWidgetCreationComplete = false; #ifdef XP_MACOSX mSentInitialTopLevelWindowEvent = false; mLastWindowIsActive = false; mLastContentFocused = false; mLastScaleFactor = 1.0;
--- a/dom/push/PushNotifier.cpp +++ b/dom/push/PushNotifier.cpp @@ -423,20 +423,17 @@ PushMessage::Json(JSContext* aCx, JS::MutableHandle<JS::Value> aResult) { nsresult rv = EnsureDecodedText(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } ErrorResult error; BodyUtil::ConsumeJson(aCx, aResult, mDecodedText, error); - if (error.Failed()) { - return error.StealNSResult(); - } - return NS_OK; + return error.StealNSResult(); } NS_IMETHODIMP PushMessage::Binary(uint32_t* aDataLen, uint8_t** aData) { if (!aDataLen || !aData) { return NS_ERROR_INVALID_ARG; }
new file mode 100644 --- /dev/null +++ b/dom/push/test/xpcshell/test_observer_data.js @@ -0,0 +1,40 @@ +'use strict'; + +var pushNotifier = Cc['@mozilla.org/push/Notifier;1'] + .getService(Ci.nsIPushNotifier); +var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); + +function run_test() { + run_next_test(); +} + +add_task(function* test_notifyWithData() { + let textData = '{"hello":"world"}'; + let data = new TextEncoder('utf-8').encode(textData); + + let notifyPromise = + promiseObserverNotification(PushServiceComponent.pushTopic); + pushNotifier.notifyPushWithData('chrome://notify-test', systemPrincipal, + '' /* messageId */, data.length, data); + + let message = (yield notifyPromise).subject.QueryInterface(Ci.nsIPushMessage); + deepEqual(message.json(), { + hello: 'world', + }, 'Should extract JSON values'); + deepEqual(message.binary(), Array.from(data), + 'Should extract raw binary data'); + equal(message.text(), textData, 'Should extract text data'); +}); + +add_task(function* test_empty_notifyWithData() { + let notifyPromise = + promiseObserverNotification(PushServiceComponent.pushTopic); + pushNotifier.notifyPushWithData('chrome://notify-test', systemPrincipal, + '' /* messageId */, 0, null); + + let message = (yield notifyPromise).subject.QueryInterface(Ci.nsIPushMessage); + throws(_ => message.json(), + 'Should throw an error when parsing an empty string as JSON'); + strictEqual(message.text(), '', 'Should return an empty string'); + deepEqual(message.binary(), [], 'Should return an empty array'); +});
--- a/dom/push/test/xpcshell/xpcshell.ini +++ b/dom/push/test/xpcshell/xpcshell.ini @@ -11,16 +11,17 @@ skip-if = toolkit == 'android' [test_handler_service.js] support-files = PushServiceHandler.js PushServiceHandler.manifest [test_notification_ack.js] [test_notification_data.js] [test_notification_duplicate.js] [test_notification_error.js] [test_notification_incomplete.js] [test_notification_version_string.js] +[test_observer_data.js] [test_permissions.js] run-sequentially = This will delete all existing push subscriptions. [test_quota_exceeded.js] [test_quota_observer.js] [test_quota_with_notification.js] [test_register_case.js]
deleted file mode 100644 --- a/dom/requestsync/RequestSync.manifest +++ /dev/null @@ -1,5 +0,0 @@ -component {8ee5ab74-15c4-478f-9d32-67627b9f0f1a} RequestSyncScheduler.js -contract @mozilla.org/dom/request-sync-scheduler;1 {8ee5ab74-15c4-478f-9d32-67627b9f0f1a} - -component {e6f55080-e549-4e30-9d00-15f240fb763c} RequestSyncManager.js -contract @mozilla.org/dom/request-sync-manager;1 {e6f55080-e549-4e30-9d00-15f240fb763c}
deleted file mode 100644 --- a/dom/requestsync/RequestSyncApp.jsm +++ /dev/null @@ -1,48 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -this.EXPORTED_SYMBOLS = ['RequestSyncApp']; - -function debug(s) { - //dump('DEBUG RequestSyncApp: ' + s + '\n'); -} - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); - -this.RequestSyncApp = function(aData) { - debug('created'); - - let keys = [ 'origin', 'manifestURL', 'isInBrowserElement' ]; - for (let i = 0; i < keys.length; ++i) { - if (!(keys[i] in aData)) { - dump("ERROR - RequestSyncApp must receive a full app object: " + keys[i] + " missing."); - throw "ERROR!"; - } - - this["_" + keys[i]] = aData[keys[i]]; - } -} - -this.RequestSyncApp.prototype = { - classDescription: 'RequestSyncApp XPCOM Component', - classID: Components.ID('{5a0b64db-a2be-4f08-a6c5-8bf2e3ae0c57}'), - contractID: '@mozilla.org/dom/request-sync-manager;1', - QueryInterface: XPCOMUtils.generateQI([]), - - get origin() { - return this._origin; - }, - - get manifestURL() { - return this._manifestURL; - }, - - get isInBrowserElement() { - return this._isInBrowserElement; - } -};
deleted file mode 100644 --- a/dom/requestsync/RequestSyncManager.js +++ /dev/null @@ -1,134 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -function debug(s) { - //dump('DEBUG RequestSyncManager: ' + s + '\n'); -} - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); -Cu.import('resource://gre/modules/RequestSyncApp.jsm'); -Cu.import('resource://gre/modules/RequestSyncTask.jsm'); - -XPCOMUtils.defineLazyServiceGetter(this, "cpmm", - "@mozilla.org/childprocessmessagemanager;1", - "nsIMessageSender"); - -function RequestSyncManager() { - debug('created'); -} - -RequestSyncManager.prototype = { - __proto__: DOMRequestIpcHelper.prototype, - - classDescription: 'RequestSyncManager XPCOM Component', - classID: Components.ID('{e6f55080-e549-4e30-9d00-15f240fb763c}'), - contractID: '@mozilla.org/dom/request-sync-manager;1', - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, - Ci.nsIObserver, - Ci.nsIDOMGlobalPropertyInitializer]), - - _messages: [ "RequestSyncManager:Registrations:Return", - "RequestSyncManager:SetPolicy:Return", - "RequestSyncManager:RunTask:Return" ], - - init: function(aWindow) { - debug("init"); - - // DOMRequestIpcHelper.initHelper sets this._window - this.initDOMRequestHelper(aWindow, this._messages); - }, - - sendMessage: function(aMsg, aObj) { - let self = this; - return this.createPromiseWithId(function(aResolverId) { - aObj.requestID = aResolverId; - cpmm.sendAsyncMessage(aMsg, aObj, null, - self._window.document.nodePrincipal); - }); - }, - - registrations: function() { - debug('registrations'); - return this.sendMessage("RequestSyncManager:Registrations", {}); - }, - - setPolicy: function(aTask, aOrigin, aManifestURL, aIsInIsolatedMozBrowserElement, - aState, aOverwrittenMinInterval) { - debug('setPolicy'); - - return this.sendMessage("RequestSyncManager:SetPolicy", - { task: aTask, - origin: aOrigin, - manifestURL: aManifestURL, - isInBrowserElement: aIsInIsolatedMozBrowserElement, - state: aState, - overwrittenMinInterval: aOverwrittenMinInterval }); - }, - - runTask: function(aTask, aOrigin, aManifestURL, aIsInIsolatedMozBrowserElement) { - debug('runTask'); - - return this.sendMessage("RequestSyncManager:RunTask", - { task: aTask, - origin: aOrigin, - manifestURL: aManifestURL, - isInBrowserElement: aIsInIsolatedMozBrowserElement }); - }, - - registrationsResult: function(aData) { - debug("registrationsResult"); - - let results = new this._window.Array(); - for (let i = 0; i < aData.length; ++i) { - if (!("app" in aData[i])) { - dump("ERROR - Serialization error in RequestSyncManager.\n"); - continue; - } - - let app = new RequestSyncApp(aData[i].app); - let exposedApp = - this._window.RequestSyncApp._create(this._window, app); - - let task = new RequestSyncTask(this, this._window, exposedApp, aData[i]); - let exposedTask = - this._window.RequestSyncTask._create(this._window, task); - - results.push(exposedTask); - } - return results; - }, - - receiveMessage: function(aMessage) { - debug('receiveMessage'); - - let req = this.getPromiseResolver(aMessage.data.requestID); - if (!req) { - return; - } - - if ('error' in aMessage.data) { - req.reject(Cu.cloneInto(aMessage.data.error, this._window)); - return; - } - - if (aMessage.name == 'RequestSyncManager:Registrations:Return') { - req.resolve(this.registrationsResult(aMessage.data.results)); - return; - } - - if ('results' in aMessage.data) { - req.resolve(Cu.cloneInto(aMessage.data.results, this._window)); - return; - } - - req.resolve(); - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RequestSyncManager]);
deleted file mode 100644 --- a/dom/requestsync/RequestSyncScheduler.js +++ /dev/null @@ -1,100 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict'; - -function debug(s) { - //dump('DEBUG RequestSyncScheduler: ' + s + '\n'); -} - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -Cu.import('resource://gre/modules/DOMRequestHelper.jsm'); -Cu.import('resource://gre/modules/XPCOMUtils.jsm'); - -XPCOMUtils.defineLazyServiceGetter(this, 'cpmm', - '@mozilla.org/childprocessmessagemanager;1', - 'nsIMessageSender'); - -function RequestSyncScheduler() { - debug('created'); -} - -RequestSyncScheduler.prototype = { - __proto__: DOMRequestIpcHelper.prototype, - - classDescription: 'RequestSyncScheduler XPCOM Component', - classID: Components.ID('{8ee5ab74-15c4-478f-9d32-67627b9f0f1a}'), - contractID: '@mozilla.org/dom/request-sync-scheduler;1', - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, - Ci.nsIObserver, - Ci.nsIDOMGlobalPropertyInitializer]), - - _messages: [ 'RequestSync:Register:Return', - 'RequestSync:Unregister:Return', - 'RequestSync:Registrations:Return', - 'RequestSync:Registration:Return' ], - - init: function(aWindow) { - debug('init'); - - // DOMRequestIpcHelper.initHelper sets this._window - this.initDOMRequestHelper(aWindow, this._messages); - }, - - register: function(aTask, aParams) { - debug('register'); - return this.sendMessage('RequestSync:Register', - { task: aTask, params: aParams }); - }, - - unregister: function(aTask) { - debug('unregister'); - return this.sendMessage('RequestSync:Unregister', - { task: aTask }); - }, - - registrations: function() { - debug('registrations'); - return this.sendMessage('RequestSync:Registrations', {}); - }, - - registration: function(aTask) { - debug('registration'); - return this.sendMessage('RequestSync:Registration', - { task: aTask }); - }, - - sendMessage: function(aMsg, aObj) { - let self = this; - return this.createPromiseWithId(function(aResolverId) { - aObj.requestID = aResolverId; - cpmm.sendAsyncMessage(aMsg, aObj, null, - self._window.document.nodePrincipal); - }); - }, - - receiveMessage: function(aMessage) { - debug('receiveMessage'); - - let req = this.getPromiseResolver(aMessage.data.requestID); - if (!req) { - return; - } - - if ('error' in aMessage.data) { - req.reject(Cu.cloneInto(aMessage.data.error, this._window)); - return; - } - - if ('results' in aMessage.data) { - req.resolve(Cu.cloneInto(aMessage.data.results, this._window)); - return; - } - - req.resolve(); - } -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RequestSyncScheduler]);
deleted file mode 100644 --- a/dom/requestsync/RequestSyncService.jsm +++ /dev/null @@ -1,1008 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -'use strict' - -const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; - -function debug(s) { - //dump('DEBUG RequestSyncService: ' + s + '\n'); -} - -const RSYNCDB_VERSION = 1; -const RSYNCDB_NAME = "requestSync"; -const RSYNC_MIN_INTERVAL = 100; - -const RSYNC_OPERATION_TIMEOUT = 120000 // 2 minutes - -const RSYNC_STATE_ENABLED = "enabled"; -const RSYNC_STATE_DISABLED = "disabled"; -const RSYNC_STATE_WIFIONLY = "wifiOnly"; - -Cu.import('resource://gre/modules/IndexedDBHelper.jsm'); -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.importGlobalProperties(["indexedDB"]); - - -XPCOMUtils.defineLazyServiceGetter(this, "appsService", - "@mozilla.org/AppsService;1", - "nsIAppsService"); - -XPCOMUtils.defineLazyServiceGetter(this, "cpmm", - "@mozilla.org/childprocessmessagemanager;1", - "nsISyncMessageSender"); - -XPCOMUtils.defineLazyServiceGetter(this, "ppmm", - "@mozilla.org/parentprocessmessagemanager;1", - "nsIMessageBroadcaster"); - -XPCOMUtils.defineLazyServiceGetter(this, "systemMessenger", - "@mozilla.org/system-message-internal;1", - "nsISystemMessagesInternal"); - -XPCOMUtils.defineLazyServiceGetter(this, "secMan", - "@mozilla.org/scriptsecuritymanager;1", - "nsIScriptSecurityManager"); - -XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService", - "@mozilla.org/power/powermanagerservice;1", - "nsIPowerManagerService"); - -XPCOMUtils.defineLazyModuleGetter(this, "AlarmService", - "resource://gre/modules/AlarmService.jsm"); - -this.RequestSyncService = { - __proto__: IndexedDBHelper.prototype, - - children: [], - - _messages: [ "RequestSync:Register", - "RequestSync:Unregister", - "RequestSync:Registrations", - "RequestSync:Registration", - "RequestSyncManager:Registrations", - "RequestSyncManager:SetPolicy", - "RequestSyncManager:RunTask" ], - - _pendingOperation: false, - _pendingMessages: [], - - _registrations: {}, - - _wifi: false, - - _activeTask: null, - _queuedTasks: [], - - _timers: {}, - _pendingRequests: {}, - - // This array contains functions to be executed after the scheduling of the - // current task or immediately if there are not scheduling in progress. - _afterSchedulingTasks: [], - - // Initialization of the RequestSyncService. - init: function() { - debug("init"); - - if (!Services.prefs.getBoolPref("dom.requestSync.enabled")) { - return; - } - - this._messages.forEach((function(msgName) { - ppmm.addMessageListener(msgName, this); - }).bind(this)); - - Services.obs.addObserver(this, 'xpcom-shutdown', false); - Services.obs.addObserver(this, 'clear-origin-data', false); - Services.obs.addObserver(this, 'wifi-state-changed', false); - - this.initDBHelper("requestSync", RSYNCDB_VERSION, [RSYNCDB_NAME]); - - // Loading all the data from the database into the _registrations map. - // Any incoming message will be stored and processed when the async - // operation is completed. - - this.dbTxn("readonly", function(aStore) { - aStore.openCursor().onsuccess = event => { - let cursor = event.target.result; - if (cursor) { - this.addRegistration(cursor.value, function() { - cursor.continue(); - }); - } - } - }.bind(this), - function() { - debug("initialization done"); - }, - function() { - dump("ERROR!! RequestSyncService - Failed to retrieve data from the database.\n"); - }); - }, - - // Shutdown the RequestSyncService. - shutdown: function() { - debug("shutdown"); - - this._messages.forEach((function(msgName) { - ppmm.removeMessageListener(msgName, this); - }).bind(this)); - - Services.obs.removeObserver(this, 'xpcom-shutdown'); - Services.obs.removeObserver(this, 'clear-origin-data'); - Services.obs.removeObserver(this, 'wifi-state-changed'); - - this.close(); - - // Removing all the registrations will delete the pending timers. - this.forEachRegistration(function(aObj) { - let key = this.principalToKey(aObj.principal); - this.removeRegistrationInternal(aObj.data.task, key); - }.bind(this)); - }, - - observe: function(aSubject, aTopic, aData) { - debug("observe"); - - switch (aTopic) { - case 'xpcom-shutdown': - this.executeAfterScheduling(function() { - this.shutdown(); - }.bind(this)); - break; - - case 'clear-origin-data': - this.executeAfterScheduling(function() { - this.clearData(aData); - }.bind(this)); - break; - - case 'wifi-state-changed': - this.executeAfterScheduling(function() { - this.wifiStateChanged(aSubject == 'enabled'); - }.bind(this)); - break; - - default: - debug("Wrong observer topic: " + aTopic); - break; - } - }, - - // When an app is uninstalled, we have to clean all its tasks. - clearData: function(aData) { - debug('clearData'); - - if (!aData) { - return; - } - - let pattern = JSON.parse(aData); - let dbKeys = []; - - for (let key in this._registrations) { - let prin = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(key); - if (!ChromeUtils.originAttributesMatchPattern(prin.originAttributes, pattern)) { - continue; - } - - for (let task in this._registrations[key]) { - dbKeys = this._registrations[key][task].dbKey; - this.removeRegistrationInternal(task, key); - } - } - - if (dbKeys.length == 0) { - return; - } - - // Remove the tasks from the database. - this.dbTxn('readwrite', function(aStore) { - for (let i = 0; i < dbKeys.length; ++i) { - aStore.delete(dbKeys[i]); - } - }, - function() { - debug("ClearData completed"); - }, function() { - debug("ClearData failed"); - }); - }, - - // Creation of the schema for the database. - upgradeSchema: function(aTransaction, aDb, aOldVersion, aNewVersion) { - debug('updateSchema'); - aDb.createObjectStore(RSYNCDB_NAME, { autoIncrement: true }); - }, - - // This method generates the key for the indexedDB object storage. - principalToKey: function(aPrincipal) { - return aPrincipal.origin; - }, - - // Add a task to the _registrations map and create the timer if it's needed. - addRegistration: function(aObj, aCb) { - debug('addRegistration'); - - let key = this.principalToKey(aObj.principal); - if (!(key in this._registrations)) { - this._registrations[key] = {}; - } - - this.scheduleTimer(aObj, function() { - this._registrations[key][aObj.data.task] = aObj; - if (aCb) { - aCb(); - } - }.bind(this)); - }, - - // Remove a task from the _registrations map and delete the timer if it's - // needed. It also checks if the principal is correct before doing the real - // operation. - removeRegistration: function(aTaskName, aKey, aPrincipal) { - debug('removeRegistration'); - - if (!(aKey in this._registrations) || - !(aTaskName in this._registrations[aKey])) { - return false; - } - - // Additional security check. - if (!aPrincipal.equals(this._registrations[aKey][aTaskName].principal)) { - return false; - } - - this.removeRegistrationInternal(aTaskName, aKey); - return true; - }, - - removeRegistrationInternal: function(aTaskName, aKey) { - debug('removeRegistrationInternal'); - - let obj = this._registrations[aKey][aTaskName]; - - this.removeTimer(obj); - - // It can be that this task has been already schedulated. - this.removeTaskFromQueue(obj); - - // It can be that this object is already in scheduled, or in the queue of a - // iDB transacation. In order to avoid rescheduling it, we must disable it. - obj.active = false; - - delete this._registrations[aKey][aTaskName]; - - // Lets remove the key in case there are not tasks registered. - for (let key in this._registrations[aKey]) { - return; - } - delete this._registrations[aKey]; - }, - - removeTaskFromQueue: function(aObj) { - let pos = this._queuedTasks.indexOf(aObj); - if (pos != -1) { - this._queuedTasks.splice(pos, 1); - }