mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-01 11:27:55 +00:00
Merge last PGO-green changeset of mozilla-inbound to mozilla-central
This commit is contained in:
commit
c48dc553b3
@ -21,7 +21,7 @@
|
||||
src="../events.js"></script>
|
||||
|
||||
<script type="application/javascript">
|
||||
gA11yEventDumpToConsole = true; // debug stuff
|
||||
//gA11yEventDumpToConsole = true; // debug stuff
|
||||
|
||||
var gLinkWindow = null;
|
||||
function closeDocChecker()
|
||||
@ -35,7 +35,9 @@
|
||||
|
||||
this.match = function closeDocChecker_match(aEvent)
|
||||
{
|
||||
return true;
|
||||
// A temporary about:blank document gets loaded before 'example.com'
|
||||
// document.
|
||||
return aEvent.DOMNode.URL == "http://www.example.com/";
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,11 +88,17 @@
|
||||
testStates("link_traversed", 0, 0, STATE_TRAVERSED);
|
||||
|
||||
// a: traversed state
|
||||
enableLogging("docload"); // debug stuff
|
||||
//enableLogging("docload"); // debug stuff
|
||||
|
||||
gQueue = new eventQueue();
|
||||
gQueue.push(new clickLink("link_traversed"));
|
||||
gQueue.onFinish = function() { gLinkWindow.close(); disableLogging(); }
|
||||
gQueue.onFinish =
|
||||
function()
|
||||
{
|
||||
gLinkWindow.close();
|
||||
//disableLogging(); // debug stuff
|
||||
}
|
||||
|
||||
gQueue.invoke(); // will call SimpleTest.finsih();
|
||||
}
|
||||
|
||||
|
@ -407,6 +407,10 @@ var shell = {
|
||||
content.removeEventListener('load', shell_homeLoaded);
|
||||
shell.isHomeLoaded = true;
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
libcutils.property_set('sys.boot_completed', '1');
|
||||
#endif
|
||||
|
||||
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
|
||||
|
||||
if ('pendingChromeEvents' in shell) {
|
||||
@ -622,13 +626,22 @@ var AlertsHelper = {
|
||||
|
||||
let topic = detail.type == "desktop-notification-click" ? "alertclickcallback"
|
||||
: "alertfinished";
|
||||
|
||||
if (uid.startsWith("app-notif")) {
|
||||
listener.mm.sendAsyncMessage("app-notification-return", {
|
||||
uid: uid,
|
||||
topic: topic,
|
||||
target: listener.target
|
||||
});
|
||||
try {
|
||||
listener.mm.sendAsyncMessage("app-notification-return", {
|
||||
uid: uid,
|
||||
topic: topic,
|
||||
target: listener.target
|
||||
});
|
||||
} catch(e) {
|
||||
gSystemMessenger.sendMessage("notification", {
|
||||
title: listener.title,
|
||||
body: listener.text,
|
||||
imageURL: listener.imageURL
|
||||
},
|
||||
Services.io.newURI(listener.target, null, null),
|
||||
Services.io.newURI(listener.manifestURL, null, null));
|
||||
}
|
||||
} else if (uid.startsWith("alert")) {
|
||||
try {
|
||||
listener.observer.observe(null, topic, listener.cookie);
|
||||
@ -937,6 +950,15 @@ window.addEventListener('ContentStart', function update_onContentStart() {
|
||||
}, "audio-channel-changed", false);
|
||||
})();
|
||||
|
||||
(function audioChannelChangedTracker() {
|
||||
Services.obs.addObserver(function(aSubject, aTopic, aData) {
|
||||
shell.sendChromeEvent({
|
||||
type: 'audio-channel-changed',
|
||||
channel: aData
|
||||
});
|
||||
}, "audio-channel-changed", false);
|
||||
})();
|
||||
|
||||
(function recordingStatusTracker() {
|
||||
let gRecordingActiveCount = 0;
|
||||
|
||||
|
@ -107,8 +107,6 @@ AlertsService.prototype = {
|
||||
Services.io.newURI(data.target, null, null),
|
||||
Services.io.newURI(listener.manifestURL, null, null));
|
||||
}
|
||||
|
||||
cpmm.sendAsyncMessage("app-notification-sysmsg-request", listener);
|
||||
}
|
||||
|
||||
// we're done with this notification
|
||||
|
@ -20,7 +20,7 @@ MOZ_NONLOCALIZED_PKG_LIST = \
|
||||
b2g \
|
||||
$(NULL)
|
||||
|
||||
MOZ_LOCALIZED_PKG_LIST = $(AB_CD)
|
||||
MOZ_LOCALIZED_PKG_LIST = $(AB_CD) multilocale
|
||||
|
||||
DEFINES += \
|
||||
-DAB_CD=$(AB_CD) \
|
||||
@ -71,6 +71,14 @@ endif
|
||||
ifdef MOZ_PKG_MANIFEST_P
|
||||
$(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) FORCE
|
||||
$(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $< > $@
|
||||
ifdef MOZ_CHROME_MULTILOCALE
|
||||
printf "\n[multilocale]\n" >> $@
|
||||
for LOCALE in $(MOZ_CHROME_MULTILOCALE) ;\
|
||||
do \
|
||||
printf "$(BINPATH)/chrome/$$LOCALE$(JAREXT)\n" >> $@; \
|
||||
printf "$(BINPATH)/chrome/$$LOCALE.manifest\n" >> $@; \
|
||||
done
|
||||
endif
|
||||
|
||||
GARBAGE += $(MOZ_PKG_MANIFEST)
|
||||
endif
|
||||
|
@ -237,14 +237,22 @@ var gPluginHandler = {
|
||||
}
|
||||
},
|
||||
|
||||
isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
|
||||
return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
|
||||
Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
|
||||
},
|
||||
|
||||
canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
|
||||
// if this isn't a known plugin, we can't activate it
|
||||
// (this also guards pluginHost.getPermissionStringForType against
|
||||
// unexpected input)
|
||||
if (!gPluginHandler.isKnownPlugin(objLoadingContent))
|
||||
return false;
|
||||
|
||||
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
|
||||
let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
if (objLoadingContent.actualType) {
|
||||
let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
|
||||
let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document);
|
||||
pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
|
||||
}
|
||||
let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
|
||||
let browser = gBrowser.getBrowserForDocument(objLoadingContent.ownerDocument.defaultView.top.document);
|
||||
let pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
|
||||
|
||||
return !objLoadingContent.activated &&
|
||||
pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
|
||||
@ -362,12 +370,14 @@ var gPluginHandler = {
|
||||
let doc = aPlugin.ownerDocument;
|
||||
let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
|
||||
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
|
||||
let pluginPermission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
if (objLoadingContent.actualType) {
|
||||
let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
|
||||
pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
|
||||
}
|
||||
// guard against giving pluginHost.getPermissionStringForType a type
|
||||
// not associated with any known plugin
|
||||
if (!gPluginHandler.isKnownPlugin(objLoadingContent))
|
||||
return;
|
||||
let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
|
||||
let pluginPermission = Services.perms.testPermission(browser.currentURI, permissionString);
|
||||
|
||||
let overlay = doc.getAnonymousElementByAttribute(aPlugin, "class", "mainBox");
|
||||
|
||||
if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
|
||||
@ -532,8 +542,9 @@ var gPluginHandler = {
|
||||
let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
|
||||
for (let plugin of aPluginList) {
|
||||
let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
if (gPluginHandler.canActivatePlugin(objLoadingContent) &&
|
||||
objLoadingContent.actualType) {
|
||||
// canActivatePlugin will return false if this isn't a known plugin type,
|
||||
// so the pluginHost.getPermissionStringForType call is protected
|
||||
if (gPluginHandler.canActivatePlugin(objLoadingContent)) {
|
||||
let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
|
||||
Services.perms.add(aBrowser.currentURI, permissionString, aPermission);
|
||||
}
|
||||
|
@ -6,6 +6,9 @@
|
||||
const PANEL_MIN_HEIGHT = 100;
|
||||
const PANEL_MIN_WIDTH = 330;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
|
||||
"resource:///modules/SharedFrame.jsm");
|
||||
|
||||
let SocialUI = {
|
||||
// Called on delayed startup to initialize UI
|
||||
init: function SocialUI_init() {
|
||||
@ -250,10 +253,13 @@ let SocialChatBar = {
|
||||
if (!SocialUI.haveLoggedInUser())
|
||||
return false;
|
||||
let docElem = document.documentElement;
|
||||
let chromeless = docElem.getAttribute("disablechrome") ||
|
||||
docElem.getAttribute("chromehidden").indexOf("extrachrome") >= 0;
|
||||
let chromeless = docElem.getAttribute("chromehidden").indexOf("extrachrome") >= 0;
|
||||
return Social.uiVisible && !chromeless;
|
||||
},
|
||||
// Does this chatbar have any chats (whether minimized, collapsed or normal)
|
||||
get hasChats() {
|
||||
return !!this.chatbar.firstElementChild;
|
||||
},
|
||||
openChat: function(aProvider, aURL, aCallback, aMode) {
|
||||
if (this.isAvailable)
|
||||
this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
|
||||
@ -675,8 +681,11 @@ var SocialToolbar = {
|
||||
|
||||
if (!SocialUI.haveLoggedInUser() || !socialEnabled) {
|
||||
let parent = document.getElementById("social-notification-panel");
|
||||
while (parent.hasChildNodes())
|
||||
parent.removeChild(parent.firstChild);
|
||||
while (parent.hasChildNodes()) {
|
||||
let frame = parent.firstChild;
|
||||
SharedFrame.forgetGroup(frame.id);
|
||||
parent.removeChild(frame);
|
||||
}
|
||||
|
||||
while (tbi.lastChild != tbi.firstChild)
|
||||
tbi.removeChild(tbi.lastChild);
|
||||
@ -754,7 +763,6 @@ var SocialToolbar = {
|
||||
str);
|
||||
}
|
||||
|
||||
let notificationFrames = document.createDocumentFragment();
|
||||
let iconContainers = document.createDocumentFragment();
|
||||
|
||||
let createdFrames = [];
|
||||
@ -764,22 +772,32 @@ var SocialToolbar = {
|
||||
|
||||
let notificationFrameId = "social-status-" + icon.name;
|
||||
let notificationFrame = document.getElementById(notificationFrameId);
|
||||
|
||||
if (!notificationFrame) {
|
||||
notificationFrame = document.createElement("iframe");
|
||||
notificationFrame.setAttribute("type", "content");
|
||||
notificationFrame.setAttribute("class", "social-panel-frame");
|
||||
notificationFrame.setAttribute("id", notificationFrameId);
|
||||
notificationFrame.setAttribute("mozbrowser", "true");
|
||||
// work around bug 793057 - by making the panel roughly the final size
|
||||
// we are more likely to have the anchor in the correct position.
|
||||
notificationFrame.style.width = PANEL_MIN_WIDTH + "px";
|
||||
|
||||
notificationFrame = SharedFrame.createFrame(
|
||||
notificationFrameId, /* frame name */
|
||||
panel, /* parent */
|
||||
{
|
||||
"type": "content",
|
||||
"mozbrowser": "true",
|
||||
"class": "social-panel-frame",
|
||||
"id": notificationFrameId,
|
||||
|
||||
// work around bug 793057 - by making the panel roughly the final size
|
||||
// we are more likely to have the anchor in the correct position.
|
||||
"style": "width: " + PANEL_MIN_WIDTH + "px;",
|
||||
|
||||
"origin": provider.origin,
|
||||
"src": icon.contentPanel
|
||||
}
|
||||
);
|
||||
|
||||
createdFrames.push(notificationFrame);
|
||||
notificationFrames.appendChild(notificationFrame);
|
||||
} else {
|
||||
notificationFrame.setAttribute("origin", provider.origin);
|
||||
SharedFrame.updateURL(notificationFrameId, icon.contentPanel);
|
||||
}
|
||||
notificationFrame.setAttribute("origin", provider.origin);
|
||||
if (notificationFrame.getAttribute("src") != icon.contentPanel)
|
||||
notificationFrame.setAttribute("src", icon.contentPanel);
|
||||
|
||||
let iconId = "social-notification-icon-" + icon.name;
|
||||
let imageId = iconId + "-image";
|
||||
@ -830,7 +848,6 @@ var SocialToolbar = {
|
||||
|
||||
image.style.listStyleImage = "url(" + icon.iconURL + ")";
|
||||
}
|
||||
panel.appendChild(notificationFrames);
|
||||
iconBox.appendChild(iconContainers);
|
||||
|
||||
for (let frame of createdFrames) {
|
||||
@ -853,6 +870,9 @@ var SocialToolbar = {
|
||||
let notificationFrameId = aToolbarButtonBox.getAttribute("notificationFrameId");
|
||||
let notificationFrame = document.getElementById(notificationFrameId);
|
||||
|
||||
let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
|
||||
SharedFrame.setOwner(notificationFrameId, notificationFrame);
|
||||
|
||||
// Clear dimensions on all browsers so the panel size will
|
||||
// only use the selected browser.
|
||||
let frameIter = panel.firstElementChild;
|
||||
@ -881,7 +901,7 @@ var SocialToolbar = {
|
||||
aToolbarButtonBox.setAttribute("open", "true");
|
||||
notificationFrame.docShell.isActive = true;
|
||||
notificationFrame.docShell.isAppTab = true;
|
||||
if (notificationFrame.contentDocument.readyState == "complete") {
|
||||
if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
|
||||
dynamicResizer.start(panel, notificationFrame);
|
||||
dispatchPanelEvent("socialFrameShow");
|
||||
} else {
|
||||
|
@ -3756,6 +3756,8 @@
|
||||
if (draggedTab.parentNode != this || event.shiftKey)
|
||||
this.selectedItem = newTab;
|
||||
} else if (draggedTab && draggedTab.parentNode == this) {
|
||||
this._finishAnimateTabMove();
|
||||
|
||||
// actually move the dragged tab
|
||||
if ("animDropIndex" in draggedTab._dragData) {
|
||||
let newIndex = draggedTab._dragData.animDropIndex;
|
||||
@ -3763,7 +3765,6 @@
|
||||
newIndex--;
|
||||
this.tabbrowser.moveTabTo(draggedTab, newIndex);
|
||||
}
|
||||
this._finishAnimateTabMove();
|
||||
} else if (draggedTab) {
|
||||
// swap the dropped tab with a new one we create and then close
|
||||
// it in the other window (making it seem to have moved between
|
||||
|
@ -274,6 +274,7 @@ _BROWSER_FILES = \
|
||||
browser_bug734076.js \
|
||||
browser_bug812562.js \
|
||||
browser_bug818009.js \
|
||||
browser_bug818118.js \
|
||||
blockPluginVulnerableUpdatable.xml \
|
||||
blockPluginVulnerableNoUpdate.xml \
|
||||
blockNoPlugins.xml \
|
||||
|
41
browser/base/content/test/browser_bug818118.js
Normal file
41
browser/base/content/test/browser_bug818118.js
Normal file
@ -0,0 +1,41 @@
|
||||
var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
|
||||
var gTestBrowser = null;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
registerCleanupFunction(function() {
|
||||
Services.prefs.clearUserPref("plugins.click_to_play");
|
||||
gTestBrowser.removeEventListener("load", pageLoad, true);
|
||||
});
|
||||
|
||||
Services.prefs.setBoolPref("plugins.click_to_play", true);
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
gTestBrowser = gBrowser.selectedBrowser;
|
||||
gTestBrowser.addEventListener("load", pageLoad, true);
|
||||
gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_both.html";
|
||||
}
|
||||
|
||||
function pageLoad(aEvent) {
|
||||
// The plugin events are async dispatched and can come after the load event
|
||||
// This just allows the events to fire before we then go on to test the states
|
||||
executeSoon(actualTest);
|
||||
}
|
||||
|
||||
function actualTest() {
|
||||
var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
|
||||
ok(popupNotification, "should have a click-to-play notification");
|
||||
var plugin = gTestBrowser.contentDocument.getElementById("test");
|
||||
ok(plugin, "should have known plugin in page");
|
||||
var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
|
||||
is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
|
||||
ok(!objLoadingContent.activated, "plugin should not be activated");
|
||||
|
||||
var unknown = gTestBrowser.contentDocument.getElementById("unknown");
|
||||
ok(unknown, "should have unknown plugin in page");
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
window.focus();
|
||||
finish();
|
||||
}
|
@ -434,6 +434,50 @@ var tests = {
|
||||
port.postMessage({topic: "test-init"});
|
||||
},
|
||||
|
||||
testChatWindowChooser: function(next) {
|
||||
// Tests that when a worker creates a chat, it is opened in the correct
|
||||
// window.
|
||||
const chatUrl = "https://example.com/browser/browser/base/content/test/social_chat.html";
|
||||
let chatId = 1;
|
||||
let port = Social.provider.getWorkerPort();
|
||||
port.postMessage({topic: "test-init"});
|
||||
|
||||
function openChat(callback) {
|
||||
port.onmessage = function(e) {
|
||||
if (e.data.topic == "got-chatbox-message")
|
||||
callback();
|
||||
}
|
||||
let url = chatUrl + "?" + (chatId++);
|
||||
port.postMessage({topic: "test-worker-chat", data: url});
|
||||
}
|
||||
|
||||
// open a chat (it will open in the main window)
|
||||
ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
|
||||
openChat(function() {
|
||||
ok(window.SocialChatBar.hasChats, "first window has the chat");
|
||||
// create a second window - although this will be the "most recent",
|
||||
// the fact the first window has a chat open means the first will be targetted.
|
||||
let secondWindow = OpenBrowserWindow();
|
||||
secondWindow.addEventListener("load", function loadListener() {
|
||||
secondWindow.removeEventListener("load", loadListener);
|
||||
ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
|
||||
openChat(function() {
|
||||
ok(!secondWindow.SocialChatBar.hasChats, "second window still has no chats");
|
||||
is(window.SocialChatBar.chatbar.childElementCount, 2, "first window now has 2 chats");
|
||||
window.SocialChatBar.chatbar.removeAll();
|
||||
// now open another chat - it should open in the second window (as
|
||||
// it is the "most recent" and no other windows have chats)
|
||||
openChat(function() {
|
||||
ok(!window.SocialChatBar.hasChats, "first window has no chats");
|
||||
ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
|
||||
secondWindow.close();
|
||||
next();
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// XXX - note this must be the last test until we restore the login state
|
||||
// between tests...
|
||||
testCloseOnLogout: function(next) {
|
||||
|
@ -26,6 +26,7 @@ EXTRA_JS_MODULES = \
|
||||
webappsUI.jsm \
|
||||
webrtcUI.jsm \
|
||||
KeywordURLResetPrompter.jsm \
|
||||
SharedFrame.jsm \
|
||||
$(NULL)
|
||||
|
||||
EXTRA_PP_JS_MODULES = RecentWindow.jsm
|
||||
|
221
browser/modules/SharedFrame.jsm
Normal file
221
browser/modules/SharedFrame.jsm
Normal file
@ -0,0 +1,221 @@
|
||||
/* 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 = [ "SharedFrame" ];
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
/**
|
||||
* The purpose of this module is to create and group various iframe
|
||||
* elements that are meant to all display the same content and only
|
||||
* one at a time. This makes it possible to have the content loaded
|
||||
* only once, while the other iframes can be kept as placeholders to
|
||||
* quickly move the content to them through the swapFrameLoaders function
|
||||
* when another one of the placeholder is meant to be displayed.
|
||||
* */
|
||||
|
||||
let Frames = new Map();
|
||||
|
||||
/**
|
||||
* The Frames map is the main data structure that holds information
|
||||
* about the groups being tracked. Each entry's key is the group name,
|
||||
* and the object holds information about what is the URL being displayed
|
||||
* on that group, and what is the active element on the group (the frame that
|
||||
* holds the loaded content).
|
||||
* The reference to the activeFrame is a weak reference, which allows the
|
||||
* frame to go away at any time, and when that happens the module considers that
|
||||
* there are no active elements in that group. The group can be reactivated
|
||||
* by changing the URL, calling preload again or adding a new element.
|
||||
*
|
||||
*
|
||||
* Frames = {
|
||||
* "messages-panel": {
|
||||
* url: string,
|
||||
* activeFrame: weakref
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Each object on the map is called a _SharedFrameGroup, which is an internal
|
||||
* class of this module which does not automatically keep track of its state. This
|
||||
* object should not be used externally, and all control should be handled by the
|
||||
* module's functions.
|
||||
*/
|
||||
|
||||
function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,<!-- Unloaded frame " + aStr + " -->";
|
||||
|
||||
|
||||
this.SharedFrame = {
|
||||
/**
|
||||
* Creates an iframe element and track it as part of the specified group
|
||||
* The module must create the iframe itself because it needs to do some special
|
||||
* handling for the element's src attribute.
|
||||
*
|
||||
* @param aGroupName the name of the group to which this frame belongs
|
||||
* @param aParent the parent element to which the frame will be appended to
|
||||
* @param aFrameAttributes an object with a list of attributes to set in the iframe
|
||||
* before appending it to the DOM. The "src" attribute has
|
||||
* special meaning here and if it's not blank it specifies
|
||||
* the URL that will be initially assigned to this group
|
||||
* @param aPreload optional, tells if the URL specified in the src attribute
|
||||
* should be preloaded in the frame being created, in case
|
||||
* it's not yet preloaded in any other frame of the group.
|
||||
* This parameter has no meaning if src is blank.
|
||||
*/
|
||||
createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) {
|
||||
let frame = aParent.ownerDocument.createElement("iframe");
|
||||
|
||||
for (let [key, val] of Iterator(aFrameAttributes)) {
|
||||
frame.setAttribute(key, val);
|
||||
}
|
||||
|
||||
let src = aFrameAttributes.src;
|
||||
if (!src) {
|
||||
aPreload = false;
|
||||
}
|
||||
|
||||
let group = Frames.get(aGroupName);
|
||||
|
||||
if (group) {
|
||||
// If this group has already been created
|
||||
|
||||
if (aPreload && !group.isAlive) {
|
||||
// If aPreload is set and the group is not already loaded, load it.
|
||||
// This can happen if:
|
||||
// - aPreload was not used while creating the previous frames of this group, or
|
||||
// - the previously active frame went dead in the meantime
|
||||
group.url = src;
|
||||
this.preload(aGroupName, frame);
|
||||
} else {
|
||||
// If aPreload is not set, or the group is already loaded in a different frame,
|
||||
// there's not much that we need to do here: just create this frame as an
|
||||
// inactivate placeholder
|
||||
frame.setAttribute("src", UNLOADED_URL(aGroupName));
|
||||
}
|
||||
|
||||
} else {
|
||||
// This is the first time we hear about this group, so let's start tracking it,
|
||||
// and also preload it if the src attribute was set and aPreload = true
|
||||
group = new _SharedFrameGroup(src);
|
||||
Frames.set(aGroupName, group);
|
||||
|
||||
if (aPreload) {
|
||||
this.preload(aGroupName, frame);
|
||||
} else {
|
||||
frame.setAttribute("src", UNLOADED_URL(aGroupName));
|
||||
}
|
||||
}
|
||||
|
||||
aParent.appendChild(frame);
|
||||
return frame;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Function that moves the loaded content from one active frame to
|
||||
* another one that is currently a placeholder. If there's no active
|
||||
* frame in the group, the content is loaded/reloaded.
|
||||
*
|
||||
* @param aGroupName the name of the group
|
||||
* @param aTargetFrame the frame element to which the content should
|
||||
* be moved to.
|
||||
*/
|
||||
setOwner: function (aGroupName, aTargetFrame) {
|
||||
let group = Frames.get(aGroupName);
|
||||
let frame = group.activeFrame;
|
||||
|
||||
if (frame == aTargetFrame) {
|
||||
// nothing to do here
|
||||
return;
|
||||
}
|
||||
|
||||
if (group.isAlive) {
|
||||
// Move document ownership to the desired frame, and make it the active one
|
||||
frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame);
|
||||
group.activeFrame = aTargetFrame;
|
||||
} else {
|
||||
// Previous owner was dead, reload the document at the new owner and make it the active one
|
||||
aTargetFrame.setAttribute("src", group.url);
|
||||
group.activeFrame = aTargetFrame;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Updates the current URL in use by this group, and loads it into the active frame.
|
||||
*
|
||||
* @param aGroupName the name of the group
|
||||
* @param aURL the new url
|
||||
*/
|
||||
updateURL: function (aGroupName, aURL) {
|
||||
let group = Frames.get(aGroupName);
|
||||
group.url = aURL;
|
||||
|
||||
if (group.isAlive) {
|
||||
group.activeFrame.setAttribute("src", aURL);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Loads the group's url into a target frame, if the group doesn't have a currently
|
||||
* active frame.
|
||||
*
|
||||
* @param aGroupName the name of the group
|
||||
* @param aTargetFrame the frame element which should be made active and
|
||||
* have the group's content loaded to
|
||||
*/
|
||||
preload: function (aGroupName, aTargetFrame) {
|
||||
let group = Frames.get(aGroupName);
|
||||
if (!group.isAlive) {
|
||||
aTargetFrame.setAttribute("src", group.url);
|
||||
group.activeFrame = aTargetFrame;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Tells if a group currently have an active element.
|
||||
*
|
||||
* @param aGroupName the name of the group
|
||||
*/
|
||||
isGroupAlive: function (aGroupName) {
|
||||
return Frames.get(aGroupName).isAlive;
|
||||
},
|
||||
|
||||
/**
|
||||
* Forgets about this group. This function doesn't need to be used
|
||||
* unless the group's name needs to be reused.
|
||||
*
|
||||
* @param aGroupName the name of the group
|
||||
*/
|
||||
forgetGroup: function (aGroupName) {
|
||||
Frames.delete(aGroupName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function _SharedFrameGroup(aURL) {
|
||||
this.url = aURL;
|
||||
this._activeFrame = null;
|
||||
}
|
||||
|
||||
_SharedFrameGroup.prototype = {
|
||||
get isAlive() {
|
||||
let frame = this.activeFrame;
|
||||
return !!(frame &&
|
||||
frame.contentDocument &&
|
||||
frame.contentDocument.location);
|
||||
},
|
||||
|
||||
get activeFrame() {
|
||||
return this._activeFrame &&
|
||||
this._activeFrame.get();
|
||||
},
|
||||
|
||||
set activeFrame(aActiveFrame) {
|
||||
this._activeFrame = aActiveFrame
|
||||
? Cu.getWeakReference(aActiveFrame)
|
||||
: null;
|
||||
}
|
||||
}
|
@ -8,6 +8,10 @@ srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
DIRS = \
|
||||
chrome \
|
||||
$(NULL)
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
XPCSHELL_TESTS = unit
|
||||
|
18
browser/modules/test/chrome/Makefile.in
Normal file
18
browser/modules/test/chrome/Makefile.in
Normal file
@ -0,0 +1,18 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MOCHITEST_CHROME_FILES = \
|
||||
test_sharedframe.xul \
|
||||
sharedframe.xul \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
174
browser/modules/test/chrome/sharedframe.xul
Normal file
174
browser/modules/test/chrome/sharedframe.xul
Normal file
@ -0,0 +1,174 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<window title="Test SharedFrame - Bug 811247"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
onload="runTest();">
|
||||
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
|
||||
function is(a,b,c) opener.wrappedJSObject.is(a,b,c);
|
||||
function ok(a,b) opener.wrappedJSObject.ok(a,b);
|
||||
function done() opener.wrappedJSObject.done();
|
||||
|
||||
Components.utils.import("resource:///modules/SharedFrame.jsm");
|
||||
ok(SharedFrame, "SharedFrame module exists");
|
||||
|
||||
let box, gGen;
|
||||
function runTest() {
|
||||
box = document.getElementById("frames-container");
|
||||
gGen = test_module();
|
||||
gGen.next();
|
||||
}
|
||||
|
||||
function test_module() {
|
||||
// note: no 'src' attribute means aPreload = false;
|
||||
let frame1 = SharedFrame.createFrame("group1", box, {id: "group1-frame1", type: "content"});
|
||||
let frame2 = SharedFrame.createFrame("group1", box, {id: "group1-frame2", type: "content"});
|
||||
let frame3 = SharedFrame.createFrame("group1", box, {id: "group1-frame3", type: "content"});
|
||||
|
||||
// Check proper attribute assignment
|
||||
is(frame1.id, "group1-frame1", "correct id");
|
||||
is(frame2.id, "group1-frame2", "correct id");
|
||||
is(frame3.id, "group1-frame3", "correct id");
|
||||
|
||||
is(frame1.getAttribute("type"), "content", "correct type");
|
||||
is(frame2.getAttribute("type"), "content", "correct type");
|
||||
is(frame3.getAttribute("type"), "content", "correct type");
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame1, frame2, frame3]);
|
||||
|
||||
// Check for unloaded in the src URL
|
||||
ok(/Unloaded/.test(frame1.contentDocument.location), "frame 1 is unloaded");
|
||||
ok(/Unloaded/.test(frame2.contentDocument.location), "frame 2 is unloaded");
|
||||
ok(/Unloaded/.test(frame3.contentDocument.location), "frame 3 is unloaded");
|
||||
|
||||
// Check that there is no frame alive in the group
|
||||
ok(!SharedFrame.isGroupAlive("group1"), "group 1 is not alive");
|
||||
|
||||
// Set the URL and load the group
|
||||
SharedFrame.updateURL("group1", "http://www.example.com");
|
||||
SharedFrame.preload("group1", frame1);
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame1]);
|
||||
|
||||
// Check that frame 1 was properly loaded and the group is alive
|
||||
ok(SharedFrame.isGroupAlive("group1"), "group 1 is now alive");
|
||||
ok(!/Unloaded/.test(frame1.contentDocument.location), "frame 1 is now loaded");
|
||||
ok(/Unloaded/.test(frame2.contentDocument.location), "frame 2 is unloaded");
|
||||
ok(/Unloaded/.test(frame3.contentDocument.location), "frame 3 is unloaded");
|
||||
|
||||
// Move content to frame 2
|
||||
SharedFrame.setOwner("group1", frame2);
|
||||
|
||||
ok(/Unloaded/.test(frame1.contentDocument.location), "frame 1 is unloaded");
|
||||
ok(!/Unloaded/.test(frame2.contentDocument.location), "content was transfered to frame 2");
|
||||
ok(/Unloaded/.test(frame3.contentDocument.location), "frame 3 is unloaded");
|
||||
|
||||
// Update URL and check that new content got loaded
|
||||
SharedFrame.updateURL("group1", "http://www.example.com/new");
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame2]);
|
||||
|
||||
ok(/new$/.test(frame2.contentDocument.location), "new url loaded");
|
||||
|
||||
// Now remove the loaded content and check if the group is properly reported as unloaded
|
||||
box.removeChild(frame2);
|
||||
ok(!SharedFrame.isGroupAlive("group1"), "group 1 is not alive");
|
||||
|
||||
// And see if setOwnering will reload the group
|
||||
SharedFrame.setOwner("group1", frame3);
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame3]);
|
||||
|
||||
ok(SharedFrame.isGroupAlive("group1"), "group 1 is alive");
|
||||
ok(/new$/.test(frame3.contentDocument.location), "content was transfered to frame 3");
|
||||
|
||||
// Create a second group to verify it doesn't interact with the first one. Also test
|
||||
// that preloading works
|
||||
let frame4 = SharedFrame.createFrame("group2", box, {src: "http://www.example.com/group2", type: "content"});
|
||||
let frame5 = SharedFrame.createFrame("group2", box, {src: "http://www.example.com/group2", type: "content"});
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame4, frame5]);
|
||||
|
||||
ok(SharedFrame.isGroupAlive("group2"), "group 2 was preloaded due to the src attribute");
|
||||
|
||||
// Check for unloaded in the src URL
|
||||
ok(/group2$/.test(frame4.contentDocument.location), "frame 4 is loaded");
|
||||
ok(/Unloaded/.test(frame5.contentDocument.location), "frame 5 is unloaded");
|
||||
|
||||
SharedFrame.setOwner("group2", frame5);
|
||||
|
||||
ok(/Unloaded/.test(frame4.contentDocument.location), "frame 4 is unloaded");
|
||||
ok(/group2$/.test(frame5.contentDocument.location), "frame 5 is loaded");
|
||||
|
||||
SharedFrame.updateURL("group2", "http://www.example.com/new2");
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame5]);
|
||||
|
||||
ok(/new2$/.test(frame5.contentDocument.location), "frame 5 changed");
|
||||
ok(/Unloaded/.test(frame1.contentDocument.location), "frame 1 still has its previous value");
|
||||
ok(/new$/.test(frame3.contentDocument.location), "frame 3 still has its previous value");
|
||||
|
||||
//And now check that aPreload parameter works
|
||||
let frame7 = SharedFrame.createFrame("group3", box, {src: "http://www.example.com/group3", type: "content"}, false);
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame7]);
|
||||
|
||||
ok(!SharedFrame.isGroupAlive("group3"), "aPreload = false works");
|
||||
ok(/Unloaded/.test(frame7.contentDocument.location), "frame 7 is unloaded");
|
||||
|
||||
let frame8 = SharedFrame.createFrame("group3", box, {src: "http://www.example.com/group3", type: "content"});
|
||||
|
||||
//--------------------------
|
||||
yield waitForLoad([frame8]);
|
||||
|
||||
ok(SharedFrame.isGroupAlive("group3"), "aPreload defauls to true");
|
||||
ok(/group3/.test(frame8.contentDocument.location), "aPreload + src loads/reloads group");
|
||||
|
||||
done();
|
||||
}
|
||||
|
||||
|
||||
function waitForLoad(frames) {
|
||||
let count = frames.length;
|
||||
for (let frame of frames) {
|
||||
let f = frame;
|
||||
f.addEventListener("DOMContentLoaded", function frameloaded(event) {
|
||||
f.removeEventListener("DOMContentLoaded", frameloaded, false);
|
||||
if (--count == 0) {
|
||||
try { gGen.next() } catch (ex if ex instanceof StopIteration) { }
|
||||
}
|
||||
}, false);
|
||||
}
|
||||
}
|
||||
]]>
|
||||
</script>
|
||||
|
||||
<box id="frames-container"/>
|
||||
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display:none;"></div>
|
||||
<pre id="test"></pre>
|
||||
</body>
|
||||
<label id="test-result"/>
|
||||
</window>
|
41
browser/modules/test/chrome/test_sharedframe.xul
Normal file
41
browser/modules/test/chrome/test_sharedframe.xul
Normal file
@ -0,0 +1,41 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
|
||||
type="text/css"?>
|
||||
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=811247
|
||||
-->
|
||||
<window title="Mozilla Bug 811247"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript"
|
||||
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=811247"
|
||||
target="_blank">Mozilla Bug 811247</a>
|
||||
</body>
|
||||
|
||||
<script type="application/javascript"><![CDATA[
|
||||
|
||||
/* Test for Bug 811247
|
||||
*
|
||||
* The test must run on a separate window becase .swapFrameLoaders currently won't swap
|
||||
* iframes that are inside frames with history enabled, which is the case of the test running
|
||||
* in the content area of a regular browser window; so we need a blank xul window for that
|
||||
*/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let testWin;
|
||||
|
||||
function done() {
|
||||
testWin.close();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
addLoadEvent(function() {
|
||||
testWin = window.open("sharedframe.xul", "", "chrome,dialog,width=400,height=400");
|
||||
});
|
||||
]]></script>
|
||||
</window>
|
@ -4004,7 +4004,7 @@ panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .
|
||||
@media (min-resolution: 2dppx) {
|
||||
panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="top"],
|
||||
panel[type="arrow"][popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowbox > .panel-arrow[side="bottom"] {
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-light-vertical@2x.png") !important;
|
||||
list-style-image: url("chrome://global/skin/arrow/panelarrow-light-vertical@2x.png");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,6 +483,15 @@ private:
|
||||
nsIPrincipal* aSubjectPrincipal,
|
||||
const char* aObjectSecurityLevel);
|
||||
|
||||
/**
|
||||
* Helper for CanExecuteScripts that allows the caller to specify
|
||||
* whether execution should be allowed if cx has no
|
||||
* nsIScriptContext.
|
||||
*/
|
||||
nsresult
|
||||
CanExecuteScripts(JSContext* cx, nsIPrincipal *aPrincipal,
|
||||
bool aAllowIfNoScriptContext, bool *result);
|
||||
|
||||
nsresult
|
||||
Init();
|
||||
|
||||
|
@ -1639,7 +1639,7 @@ nsScriptSecurityManager::CheckFunctionAccess(JSContext *aCx, void *aFunObj,
|
||||
// allowed to execute scripts.
|
||||
|
||||
bool result;
|
||||
rv = CanExecuteScripts(aCx, subject, &result);
|
||||
rv = CanExecuteScripts(aCx, subject, true, &result);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
@ -1672,6 +1672,15 @@ NS_IMETHODIMP
|
||||
nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
|
||||
nsIPrincipal *aPrincipal,
|
||||
bool *result)
|
||||
{
|
||||
return CanExecuteScripts(cx, aPrincipal, false, result);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
|
||||
nsIPrincipal *aPrincipal,
|
||||
bool aAllowIfNoScriptContext,
|
||||
bool *result)
|
||||
{
|
||||
*result = false;
|
||||
|
||||
@ -1684,7 +1693,13 @@ nsScriptSecurityManager::CanExecuteScripts(JSContext* cx,
|
||||
|
||||
//-- See if the current window allows JS execution
|
||||
nsIScriptContext *scriptContext = GetScriptContext(cx);
|
||||
if (!scriptContext) return NS_ERROR_FAILURE;
|
||||
if (!scriptContext) {
|
||||
if (aAllowIfNoScriptContext) {
|
||||
*result = true;
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
if (!scriptContext->GetScriptsEnabled()) {
|
||||
// No scripting on this context, folks
|
||||
|
12
configure.in
12
configure.in
@ -4235,7 +4235,7 @@ MOZ_SAMPLE_TYPE_S16=
|
||||
MOZ_MEDIA=
|
||||
MOZ_OPUS=1
|
||||
MOZ_WEBM=1
|
||||
MOZ_DASH=
|
||||
MOZ_DASH=1
|
||||
MOZ_WEBRTC=1
|
||||
MOZ_PEERCONNECTION=
|
||||
MOZ_SRTP=
|
||||
@ -5381,12 +5381,12 @@ if test -n "$MOZ_WEBM"; then
|
||||
fi;
|
||||
|
||||
dnl ========================================================
|
||||
dnl = Enable DASH-WebM support
|
||||
dnl = Disable DASH-WebM support
|
||||
dnl ========================================================
|
||||
MOZ_ARG_ENABLE_BOOL(dash,
|
||||
[ --enable-dash Enable support for DASH-WebM],
|
||||
MOZ_DASH=1,
|
||||
MOZ_DASH=)
|
||||
MOZ_ARG_DISABLE_BOOL(dash,
|
||||
[ --disable-dash Disable support for DASH-WebM],
|
||||
MOZ_DASH=,
|
||||
MOZ_DASH=1)
|
||||
|
||||
if test -n "$MOZ_DASH"; then
|
||||
if test -n "$MOZ_WEBM"; then
|
||||
|
@ -79,8 +79,8 @@ class Element;
|
||||
} // namespace mozilla
|
||||
|
||||
#define NS_IDOCUMENT_IID \
|
||||
{ 0xd69b94c2, 0x92ed, 0x4baa, \
|
||||
{ 0x82, 0x08, 0x56, 0xe4, 0xc4, 0xb3, 0xf3, 0xc8 } }
|
||||
{ 0xcc604bdc, 0xd55e, 0x4918, \
|
||||
{ 0xaa, 0x82, 0xb2, 0xde, 0xbf, 0x01, 0x09, 0x5d } }
|
||||
|
||||
// Flag for AddStyleSheet().
|
||||
#define NS_STYLESHEET_FROM_CATALOG (1 << 0)
|
||||
@ -1669,7 +1669,9 @@ public:
|
||||
#undef DEPRECATED_OPERATION
|
||||
void WarnOnceAbout(DeprecatedOperations aOperation, bool asError = false);
|
||||
|
||||
virtual void PostVisibilityUpdateEvent() = 0;
|
||||
// This method may fire a DOM event; if it does so it will happen
|
||||
// synchronously if aFireEventSync is true, asynchronously otherwise.
|
||||
virtual void UpdateVisibilityState(bool aFireEventSync) = 0;
|
||||
|
||||
bool IsSyntheticDocument() { return mIsSyntheticDocument; }
|
||||
|
||||
|
@ -19,7 +19,7 @@
|
||||
#include "nsPropertyTable.h" // for typedefs
|
||||
#include "nsTObserverArray.h" // for member
|
||||
#include "nsWindowMemoryReporter.h" // for NS_DECL_SIZEOF_EXCLUDING_THIS
|
||||
#include "nsWrapperCache.h" // for base class
|
||||
#include "mozilla/dom/EventTarget.h" // for base class
|
||||
|
||||
// Including 'windows.h' will #define GetClassInfo to something else.
|
||||
#ifdef XP_WIN
|
||||
@ -267,8 +267,7 @@ private:
|
||||
* nsIContent and nsIDocument share. An instance of this interface has a list
|
||||
* of nsIContent children and provides access to them.
|
||||
*/
|
||||
class nsINode : public nsIDOMEventTarget,
|
||||
public nsWrapperCache
|
||||
class nsINode : public mozilla::dom::EventTarget
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_INODE_IID)
|
||||
|
@ -7063,7 +7063,7 @@ nsDocument::OnPageShow(bool aPersisted,
|
||||
SetImagesNeedAnimating(true);
|
||||
}
|
||||
|
||||
UpdateVisibilityState();
|
||||
UpdateVisibilityState(true);
|
||||
|
||||
nsCOMPtr<nsIDOMEventTarget> target = aDispatchStartTarget;
|
||||
if (!target) {
|
||||
@ -7125,7 +7125,7 @@ nsDocument::OnPageHide(bool aPersisted,
|
||||
|
||||
mVisible = false;
|
||||
|
||||
UpdateVisibilityState();
|
||||
UpdateVisibilityState(true);
|
||||
|
||||
EnumerateExternalResources(NotifyPageHide, &aPersisted);
|
||||
EnumerateFreezableElements(NotifyActivityChanged, nullptr);
|
||||
@ -9482,25 +9482,37 @@ nsDocument::GetMozPointerLockElement(nsIDOMElement** aPointerLockedElement)
|
||||
#undef TOUCH_EVENT
|
||||
#undef EVENT
|
||||
|
||||
void
|
||||
nsDocument::UpdateVisibilityState()
|
||||
/* virtual */ void
|
||||
nsDocument::UpdateVisibilityState(bool aFireEventSync)
|
||||
{
|
||||
VisibilityState oldState = mVisibilityState;
|
||||
mVisibilityState = GetVisibilityState();
|
||||
if (oldState != mVisibilityState) {
|
||||
nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
|
||||
NS_LITERAL_STRING("visibilitychange"),
|
||||
/* bubbles = */ true,
|
||||
/* cancelable = */ false);
|
||||
nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
|
||||
NS_LITERAL_STRING("mozvisibilitychange"),
|
||||
/* bubbles = */ true,
|
||||
/* cancelable = */ false);
|
||||
if (aFireEventSync) {
|
||||
FireVisibilityChangeEvent();
|
||||
} else {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsDocument::FireVisibilityChangeEvent);
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
EnumerateFreezableElements(NotifyActivityChanged, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsDocument::FireVisibilityChangeEvent()
|
||||
{
|
||||
nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
|
||||
NS_LITERAL_STRING("visibilitychange"),
|
||||
/* bubbles = */ true,
|
||||
/* cancelable = */ false);
|
||||
nsContentUtils::DispatchTrustedEvent(this, static_cast<nsIDocument*>(this),
|
||||
NS_LITERAL_STRING("mozvisibilitychange"),
|
||||
/* bubbles = */ true,
|
||||
/* cancelable = */ false);
|
||||
}
|
||||
|
||||
nsDocument::VisibilityState
|
||||
nsDocument::GetVisibilityState() const
|
||||
{
|
||||
@ -9519,14 +9531,6 @@ nsDocument::GetVisibilityState() const
|
||||
return eVisible;
|
||||
}
|
||||
|
||||
/* virtual */ void
|
||||
nsDocument::PostVisibilityUpdateEvent()
|
||||
{
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &nsDocument::UpdateVisibilityState);
|
||||
NS_DispatchToMainThread(event);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsDocument::GetMozHidden(bool* aHidden)
|
||||
{
|
||||
|
@ -995,11 +995,8 @@ public:
|
||||
bool SetPointerLock(Element* aElement, int aCursorStyle);
|
||||
static void UnlockPointer();
|
||||
|
||||
// This method may fire a DOM event; if it does so it will happen
|
||||
// synchronously.
|
||||
void UpdateVisibilityState();
|
||||
// Posts an event to call UpdateVisibilityState
|
||||
virtual void PostVisibilityUpdateEvent();
|
||||
virtual void UpdateVisibilityState(bool aFireEventSync);
|
||||
void FireVisibilityChangeEvent();
|
||||
|
||||
virtual void DocSizeOfExcludingThis(nsWindowSizes* aWindowSizes) const;
|
||||
// DocSizeOfIncludingThis is inherited from nsIDocument.
|
||||
|
62
content/events/public/EventTarget.h
Normal file
62
content/events/public/EventTarget.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_EventTarget_h_
|
||||
#define mozilla_dom_EventTarget_h_
|
||||
|
||||
#include "nsIDOMEventTarget.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/Nullable.h"
|
||||
#include "nsIDOMEvent.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// IID for the dom::Element interface
|
||||
#define NS_EVENTTARGET_IID \
|
||||
{ 0x0a5aed21, 0x0bab, 0x48b3, \
|
||||
{ 0xbe, 0x4b, 0xd4, 0xf9, 0xd4, 0xea, 0xc7, 0xdb } }
|
||||
|
||||
class EventTarget : public nsIDOMEventTarget,
|
||||
public nsWrapperCache
|
||||
{
|
||||
public:
|
||||
NS_DECLARE_STATIC_IID_ACCESSOR(NS_EVENTTARGET_IID)
|
||||
|
||||
// WebIDL API
|
||||
using nsIDOMEventTarget::AddEventListener;
|
||||
using nsIDOMEventTarget::RemoveEventListener;
|
||||
using nsIDOMEventTarget::DispatchEvent;
|
||||
void AddEventListener(const nsAString& aType,
|
||||
nsIDOMEventListener* aCallback, // XXX nullable
|
||||
bool aCapture, const Nullable<bool>& aWantsUntrusted,
|
||||
mozilla::ErrorResult& aRv)
|
||||
{
|
||||
aRv = AddEventListener(aType, aCallback, aCapture,
|
||||
!aWantsUntrusted.IsNull() && aWantsUntrusted.Value(),
|
||||
aWantsUntrusted.IsNull() ? 1 : 2);
|
||||
}
|
||||
void RemoveEventListener(const nsAString& aType,
|
||||
nsIDOMEventListener* aCallback,
|
||||
bool aCapture, mozilla::ErrorResult& aRv)
|
||||
{
|
||||
aRv = RemoveEventListener(aType, aCallback, aCapture);
|
||||
}
|
||||
bool DispatchEvent(nsIDOMEvent* aEvent, mozilla::ErrorResult& aRv)
|
||||
{
|
||||
bool result = false;
|
||||
aRv = DispatchEvent(aEvent, &result);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(EventTarget, NS_EVENTTARGET_IID)
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_EventTarget_h_
|
@ -24,6 +24,12 @@ EXPORTS = \
|
||||
nsVKList.h \
|
||||
$(NULL)
|
||||
|
||||
EXPORTS_NAMESPACES = mozilla/dom
|
||||
|
||||
EXPORTS_mozilla/dom = \
|
||||
EventTarget.h \
|
||||
$(NULL)
|
||||
|
||||
XPIDLSRCS = \
|
||||
nsIEventListenerService.idl \
|
||||
$(NULL)
|
||||
|
@ -8,21 +8,17 @@
|
||||
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsGkAtoms.h"
|
||||
#include "nsIDOMEventTarget.h"
|
||||
#include "nsIDOMEventListener.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsPIDOMWindow.h"
|
||||
#include "nsIScriptGlobalObject.h"
|
||||
#include "nsEventListenerManager.h"
|
||||
#include "nsIScriptContext.h"
|
||||
#include "nsWrapperCache.h"
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/EventTarget.h"
|
||||
|
||||
class nsDOMEvent;
|
||||
|
||||
class nsDOMEventTargetHelper : public nsIDOMEventTarget,
|
||||
public nsWrapperCache
|
||||
class nsDOMEventTargetHelper : public mozilla::dom::EventTarget
|
||||
{
|
||||
public:
|
||||
nsDOMEventTargetHelper() : mOwner(nullptr), mHasOrHasHadOwner(false) {}
|
||||
@ -31,27 +27,6 @@ public:
|
||||
NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(nsDOMEventTargetHelper)
|
||||
|
||||
NS_DECL_NSIDOMEVENTTARGET
|
||||
void AddEventListener(const nsAString& aType,
|
||||
nsIDOMEventListener* aCallback, // XXX nullable
|
||||
bool aCapture, const Nullable<bool>& aWantsUntrusted,
|
||||
mozilla::ErrorResult& aRv)
|
||||
{
|
||||
aRv = AddEventListener(aType, aCallback, aCapture,
|
||||
!aWantsUntrusted.IsNull() && aWantsUntrusted.Value(),
|
||||
aWantsUntrusted.IsNull() ? 1 : 2);
|
||||
}
|
||||
void RemoveEventListener(const nsAString& aType,
|
||||
nsIDOMEventListener* aCallback,
|
||||
bool aCapture, mozilla::ErrorResult& aRv)
|
||||
{
|
||||
aRv = RemoveEventListener(aType, aCallback, aCapture);
|
||||
}
|
||||
bool DispatchEvent(nsIDOMEvent* aEvent, mozilla::ErrorResult& aRv)
|
||||
{
|
||||
bool result = false;
|
||||
aRv = DispatchEvent(aEvent, &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
void GetParentObject(nsIScriptGlobalObject **aParentObject)
|
||||
{
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "AudioChannelCommon.h"
|
||||
#include "DecoderTraits.h"
|
||||
#include "MediaMetadataManager.h"
|
||||
#include "AudioChannelAgent.h"
|
||||
|
||||
// Define to output information on decoding and painting framerate
|
||||
/* #define DEBUG_FRAME_RATE 1 */
|
||||
@ -36,14 +37,12 @@ typedef uint16_t nsMediaReadyState;
|
||||
namespace mozilla {
|
||||
class MediaResource;
|
||||
class MediaDecoder;
|
||||
#ifdef MOZ_DASH
|
||||
class DASHDecoder;
|
||||
#endif
|
||||
}
|
||||
|
||||
class nsHTMLMediaElement : public nsGenericHTMLElement,
|
||||
public nsIObserver,
|
||||
public mozilla::MediaDecoderOwner
|
||||
public mozilla::MediaDecoderOwner,
|
||||
public nsIAudioChannelAgentCallback
|
||||
{
|
||||
public:
|
||||
typedef mozilla::TimeStamp TimeStamp;
|
||||
@ -56,10 +55,6 @@ public:
|
||||
typedef mozilla::AudioStream AudioStream;
|
||||
typedef mozilla::MediaDecoder MediaDecoder;
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
friend class DASHDecoder;
|
||||
#endif
|
||||
|
||||
mozilla::CORSMode GetCORSMode() {
|
||||
return mCORSMode;
|
||||
}
|
||||
@ -82,6 +77,8 @@ public:
|
||||
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
|
||||
|
||||
// nsISupports
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsHTMLMediaElement,
|
||||
@ -326,9 +323,6 @@ public:
|
||||
return mSrcStream->GetStream();
|
||||
}
|
||||
|
||||
// Notification from the AudioChannelService.
|
||||
nsresult NotifyAudioChannelStateChanged();
|
||||
|
||||
protected:
|
||||
class MediaLoadListener;
|
||||
class StreamListener;
|
||||
@ -612,7 +606,7 @@ protected:
|
||||
bool CheckAudioChannelPermissions(const nsAString& aType);
|
||||
|
||||
// This method does the check for muting/unmuting the audio channel.
|
||||
nsresult UpdateChannelMuteState();
|
||||
nsresult UpdateChannelMuteState(bool aCanPlay);
|
||||
|
||||
// Update the audio channel playing state
|
||||
void UpdateAudioChannelPlayingState();
|
||||
@ -897,6 +891,9 @@ protected:
|
||||
|
||||
// Is this media element playing?
|
||||
bool mPlayingThroughTheAudioChannel;
|
||||
|
||||
// An agent used to join audio channel service.
|
||||
nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -101,6 +101,7 @@
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "nsHTMLDocument.h"
|
||||
#include "nsDOMTouchEvent.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
@ -1816,6 +1817,93 @@ nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
|
||||
aDefer);
|
||||
}
|
||||
|
||||
// FIXME (https://bugzilla.mozilla.org/show_bug.cgi?id=431767)
|
||||
// nsDocument::GetInnerWindow can return an outer window in some
|
||||
// cases. We don't want to stick an event listener on an outer
|
||||
// window, so bail if it does. See also similar code in
|
||||
// nsGenericHTMLElement::GetEventListenerManagerForAttr.
|
||||
#define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
|
||||
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|
||||
EventHandlerNonNull* \
|
||||
nsGenericHTMLElement::GetOn##name_() \
|
||||
{ \
|
||||
if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) { \
|
||||
/* XXXbz note to self: add tests for this! */ \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (win && win->IsInnerWindow()) { \
|
||||
nsCOMPtr<nsISupports> supports = do_QueryInterface(win); \
|
||||
nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports); \
|
||||
return globalWin->GetOn##name_(); \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
return nsINode::GetOn##name_(); \
|
||||
} \
|
||||
void \
|
||||
nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler, \
|
||||
ErrorResult& error) \
|
||||
{ \
|
||||
if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) { \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (!win || !win->IsInnerWindow()) { \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
nsCOMPtr<nsISupports> supports = do_QueryInterface(win); \
|
||||
nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports); \
|
||||
return globalWin->SetOn##name_(handler, error); \
|
||||
} \
|
||||
\
|
||||
return nsINode::SetOn##name_(handler, error); \
|
||||
}
|
||||
#define ERROR_EVENT(name_, id_, type_, struct_) \
|
||||
already_AddRefed<EventHandlerNonNull> \
|
||||
nsGenericHTMLElement::GetOn##name_() \
|
||||
{ \
|
||||
if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) { \
|
||||
/* XXXbz note to self: add tests for this! */ \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (win && win->IsInnerWindow()) { \
|
||||
nsCOMPtr<nsISupports> supports = do_QueryInterface(win); \
|
||||
nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports); \
|
||||
OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \
|
||||
if (errorHandler) { \
|
||||
nsRefPtr<EventHandlerNonNull> handler = \
|
||||
new EventHandlerNonNull(errorHandler); \
|
||||
return handler.forget(); \
|
||||
} \
|
||||
} \
|
||||
return nullptr; \
|
||||
} \
|
||||
\
|
||||
nsRefPtr<EventHandlerNonNull> handler = nsINode::GetOn##name_(); \
|
||||
return handler.forget(); \
|
||||
} \
|
||||
void \
|
||||
nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler, \
|
||||
ErrorResult& error) \
|
||||
{ \
|
||||
if (Tag() == nsGkAtoms::body || Tag() == nsGkAtoms::frameset) { \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (!win || !win->IsInnerWindow()) { \
|
||||
return; \
|
||||
} \
|
||||
\
|
||||
nsCOMPtr<nsISupports> supports = do_QueryInterface(win); \
|
||||
nsGlobalWindow* globalWin = nsGlobalWindow::FromSupports(supports); \
|
||||
nsRefPtr<OnErrorEventHandlerNonNull> errorHandler = \
|
||||
new OnErrorEventHandlerNonNull(handler); \
|
||||
return globalWin->SetOn##name_(errorHandler, error); \
|
||||
} \
|
||||
\
|
||||
return nsINode::SetOn##name_(handler, error); \
|
||||
}
|
||||
#include "nsEventNameList.h"
|
||||
#undef ERROR_EVENT
|
||||
#undef FORWARDED_EVENT
|
||||
#undef EVENT
|
||||
|
||||
nsresult
|
||||
nsGenericHTMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
|
||||
nsIAtom* aPrefix, const nsAString& aValue,
|
||||
@ -1994,7 +2082,8 @@ nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
|
||||
}
|
||||
|
||||
mozilla::css::URLValue *url =
|
||||
new mozilla::css::URLValue(buffer, baseURI, uri, NodePrincipal());
|
||||
new mozilla::css::URLValue(uri, buffer, doc->GetDocumentURI(),
|
||||
NodePrincipal());
|
||||
aResult.SetTo(url, &aValue);
|
||||
return true;
|
||||
}
|
||||
|
@ -239,6 +239,25 @@ public:
|
||||
}
|
||||
return style;
|
||||
}
|
||||
#define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */
|
||||
// The using nsINode::Get/SetOn* are to avoid warnings about shadowing the XPCOM
|
||||
// getter and setter on nsINode.
|
||||
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|
||||
using nsINode::GetOn##name_; \
|
||||
using nsINode::SetOn##name_; \
|
||||
mozilla::dom::EventHandlerNonNull* GetOn##name_(); \
|
||||
void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler, \
|
||||
mozilla::ErrorResult& error);
|
||||
#define ERROR_EVENT(name_, id_, type_, struct_) \
|
||||
using nsINode::GetOn##name_; \
|
||||
using nsINode::SetOn##name_; \
|
||||
already_AddRefed<mozilla::dom::EventHandlerNonNull> GetOn##name_(); \
|
||||
void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler, \
|
||||
mozilla::ErrorResult& error);
|
||||
#include "nsEventNameList.h"
|
||||
#undef ERROR_EVENT
|
||||
#undef FORWARDED_EVENT
|
||||
#undef EVENT
|
||||
void GetClassName(nsAString& aClassName)
|
||||
{
|
||||
GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aClassName);
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include "nsIEditorDocShell.h"
|
||||
#include "nsRuleWalker.h"
|
||||
#include "jspubtd.h"
|
||||
#include "mozilla/dom/EventHandlerBinding.h"
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
@ -474,34 +475,42 @@ nsHTMLBodyElement::GetAssociatedEditor()
|
||||
return editor;
|
||||
}
|
||||
|
||||
// Event listener stuff
|
||||
// FIXME (https://bugzilla.mozilla.org/show_bug.cgi?id=431767)
|
||||
// nsDocument::GetInnerWindow can return an outer window in some
|
||||
// cases. We don't want to stick an event listener on an outer
|
||||
// window, so bail if it does. See also similar code in
|
||||
// nsGenericHTMLElement::GetEventListenerManagerForAttr.
|
||||
#define EVENT(name_, id_, type_, struct_) /* nothing; handled by the superclass */
|
||||
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|
||||
// nsGenericHTMLElement::GetOnError returns
|
||||
// already_AddRefed<EventHandlerNonNull> while other getters return
|
||||
// EventHandlerNonNull*, so allow passing in the type to use here.
|
||||
#define FORWARDED_EVENT_HELPER(name_, getter_type_) \
|
||||
NS_IMETHODIMP nsHTMLBodyElement::GetOn##name_(JSContext *cx, \
|
||||
jsval *vp) { \
|
||||
/* XXXbz note to self: add tests for this! */ \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (win && win->IsInnerWindow()) { \
|
||||
nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win); \
|
||||
return ev->GetOn##name_(cx, vp); \
|
||||
} \
|
||||
*vp = JSVAL_NULL; \
|
||||
getter_type_ h = nsGenericHTMLElement::GetOn##name_(); \
|
||||
vp->setObjectOrNull(h ? h->Callable() : nullptr); \
|
||||
return NS_OK; \
|
||||
} \
|
||||
NS_IMETHODIMP nsHTMLBodyElement::SetOn##name_(JSContext *cx, \
|
||||
const jsval &v) { \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (win && win->IsInnerWindow()) { \
|
||||
nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win); \
|
||||
return ev->SetOn##name_(cx, v); \
|
||||
JSObject *obj = GetWrapper(); \
|
||||
if (!obj) { \
|
||||
/* Just silently do nothing */ \
|
||||
return NS_OK; \
|
||||
} \
|
||||
return NS_OK; \
|
||||
nsRefPtr<EventHandlerNonNull> handler; \
|
||||
JSObject *callable; \
|
||||
if (v.isObject() && \
|
||||
JS_ObjectIsCallable(cx, callable = &v.toObject())) { \
|
||||
bool ok; \
|
||||
handler = new EventHandlerNonNull(cx, obj, callable, &ok); \
|
||||
if (!ok) { \
|
||||
return NS_ERROR_OUT_OF_MEMORY; \
|
||||
} \
|
||||
} \
|
||||
ErrorResult rv; \
|
||||
nsGenericHTMLElement::SetOn##name_(handler, rv); \
|
||||
return rv.ErrorCode(); \
|
||||
}
|
||||
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|
||||
FORWARDED_EVENT_HELPER(name_, EventHandlerNonNull*)
|
||||
#define ERROR_EVENT(name_, id_, type_, struct_) \
|
||||
FORWARDED_EVENT_HELPER(name_, nsCOMPtr<EventHandlerNonNull>)
|
||||
#define WINDOW_EVENT(name_, id_, type_, struct_) \
|
||||
NS_IMETHODIMP nsHTMLBodyElement::GetOn##name_(JSContext *cx, \
|
||||
jsval *vp) { \
|
||||
@ -522,5 +531,7 @@ nsHTMLBodyElement::GetAssociatedEditor()
|
||||
}
|
||||
#include "nsEventNameList.h"
|
||||
#undef WINDOW_EVENT
|
||||
#undef ERROR_EVENT
|
||||
#undef FORWARDED_EVENT
|
||||
#undef FORWARDED_EVENT_HELPER
|
||||
#undef EVENT
|
||||
|
@ -5,7 +5,9 @@
|
||||
|
||||
#include "nsHTMLFrameSetElement.h"
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/dom/EventHandlerBinding.h"
|
||||
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
NS_IMPL_NS_NEW_HTML_ELEMENT(FrameSet)
|
||||
@ -318,34 +320,42 @@ nsHTMLFrameSetElement::ParseRowCol(const nsAString & aValue,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Event listener stuff
|
||||
// FIXME (https://bugzilla.mozilla.org/show_bug.cgi?id=431767)
|
||||
// nsDocument::GetInnerWindow can return an outer window in some
|
||||
// cases. We don't want to stick an event listener on an outer
|
||||
// window, so bail if it does. See also similar code in
|
||||
// nsGenericHTMLElement::GetEventListenerManagerForAttr.
|
||||
#define EVENT(name_, id_, type_, struct_) /* nothing; handled by the shim */
|
||||
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|
||||
// nsGenericHTMLElement::GetOnError returns
|
||||
// already_AddRefed<EventHandlerNonNull> while other getters return
|
||||
// EventHandlerNonNull*, so allow passing in the type to use here.
|
||||
#define FORWARDED_EVENT_HELPER(name_, getter_type_) \
|
||||
NS_IMETHODIMP nsHTMLFrameSetElement::GetOn##name_(JSContext *cx, \
|
||||
jsval *vp) { \
|
||||
/* XXXbz note to self: add tests for this! */ \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (win && win->IsInnerWindow()) { \
|
||||
nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win); \
|
||||
return ev->GetOn##name_(cx, vp); \
|
||||
} \
|
||||
*vp = JSVAL_NULL; \
|
||||
getter_type_ h = nsGenericHTMLElement::GetOn##name_(); \
|
||||
vp->setObjectOrNull(h ? h->Callable() : nullptr); \
|
||||
return NS_OK; \
|
||||
} \
|
||||
NS_IMETHODIMP nsHTMLFrameSetElement::SetOn##name_(JSContext *cx, \
|
||||
const jsval &v) { \
|
||||
nsPIDOMWindow* win = OwnerDoc()->GetInnerWindow(); \
|
||||
if (win && win->IsInnerWindow()) { \
|
||||
nsCOMPtr<nsIInlineEventHandlers> ev = do_QueryInterface(win); \
|
||||
return ev->SetOn##name_(cx, v); \
|
||||
JSObject *obj = GetWrapper(); \
|
||||
if (!obj) { \
|
||||
/* Just silently do nothing */ \
|
||||
return NS_OK; \
|
||||
} \
|
||||
return NS_OK; \
|
||||
nsRefPtr<EventHandlerNonNull> handler; \
|
||||
JSObject *callable; \
|
||||
if (v.isObject() && \
|
||||
JS_ObjectIsCallable(cx, callable = &v.toObject())) { \
|
||||
bool ok; \
|
||||
handler = new EventHandlerNonNull(cx, obj, callable, &ok); \
|
||||
if (!ok) { \
|
||||
return NS_ERROR_OUT_OF_MEMORY; \
|
||||
} \
|
||||
} \
|
||||
ErrorResult rv; \
|
||||
nsGenericHTMLElement::SetOn##name_(handler, rv); \
|
||||
return rv.ErrorCode(); \
|
||||
}
|
||||
#define FORWARDED_EVENT(name_, id_, type_, struct_) \
|
||||
FORWARDED_EVENT_HELPER(name_, EventHandlerNonNull*)
|
||||
#define ERROR_EVENT(name_, id_, type_, struct_) \
|
||||
FORWARDED_EVENT_HELPER(name_, nsCOMPtr<EventHandlerNonNull>)
|
||||
#define WINDOW_EVENT(name_, id_, type_, struct_) \
|
||||
NS_IMETHODIMP nsHTMLFrameSetElement::GetOn##name_(JSContext *cx, \
|
||||
jsval *vp) { \
|
||||
@ -367,5 +377,7 @@ nsHTMLFrameSetElement::ParseRowCol(const nsAString & aValue,
|
||||
}
|
||||
#include "nsEventNameList.h"
|
||||
#undef WINDOW_EVENT
|
||||
#undef ERROR_EVENT
|
||||
#undef FORWARDED_EVENT
|
||||
#undef FORWARDED_EVENT_HELPER
|
||||
#undef EVENT
|
||||
|
@ -1960,6 +1960,7 @@ bool nsHTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
|
||||
{ "notification", AUDIO_CHANNEL_NOTIFICATION },
|
||||
{ "alarm", AUDIO_CHANNEL_ALARM },
|
||||
{ "telephony", AUDIO_CHANNEL_TELEPHONY },
|
||||
{ "ringer", AUDIO_CHANNEL_RINGER },
|
||||
{ "publicnotification", AUDIO_CHANNEL_PUBLICNOTIFICATION },
|
||||
{ 0 }
|
||||
};
|
||||
@ -3100,8 +3101,13 @@ void nsHTMLMediaElement::NotifyOwnerDocumentActivityChanged()
|
||||
}
|
||||
}
|
||||
|
||||
if (mPlayingThroughTheAudioChannel) {
|
||||
UpdateChannelMuteState();
|
||||
if (mPlayingThroughTheAudioChannel && mAudioChannelAgent) {
|
||||
bool hidden = false;
|
||||
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(ownerDoc);
|
||||
if (domDoc) {
|
||||
domDoc->GetHidden(&hidden);
|
||||
mAudioChannelAgent->SetVisibilityState(!hidden);
|
||||
}
|
||||
}
|
||||
|
||||
AddRemoveSelfReference();
|
||||
@ -3510,32 +3516,16 @@ ImageContainer* nsHTMLMediaElement::GetImageContainer()
|
||||
return container ? container->GetImageContainer() : nullptr;
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::UpdateChannelMuteState()
|
||||
nsresult nsHTMLMediaElement::UpdateChannelMuteState(bool aCanPlay)
|
||||
{
|
||||
// Only on B2G we mute the nsHTMLMediaElement following the rules of
|
||||
// AudioChannelService.
|
||||
#ifdef MOZ_B2G
|
||||
bool hidden = false;
|
||||
nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(OwnerDoc());
|
||||
if (!domDoc) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsresult rv = domDoc->GetHidden(&hidden);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool mute = false;
|
||||
|
||||
nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetAudioChannelService();
|
||||
if (audioChannelService) {
|
||||
mute = audioChannelService->GetMuted(mAudioChannelType, hidden);
|
||||
}
|
||||
|
||||
// We have to mute this channel:
|
||||
if (mute && !mChannelMuted) {
|
||||
if (!aCanPlay && !mChannelMuted) {
|
||||
mChannelMuted = true;
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptbegin"));
|
||||
} else if (!mute && mChannelMuted) {
|
||||
} else if (aCanPlay && mChannelMuted) {
|
||||
mChannelMuted = false;
|
||||
DispatchAsyncEvent(NS_LITERAL_STRING("mozinterruptend"));
|
||||
}
|
||||
@ -3556,22 +3546,30 @@ void nsHTMLMediaElement::UpdateAudioChannelPlayingState()
|
||||
if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
|
||||
mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
|
||||
|
||||
nsRefPtr<AudioChannelService> audioChannelService = AudioChannelService::GetAudioChannelService();
|
||||
if (!audioChannelService) {
|
||||
return;
|
||||
if (!mAudioChannelAgent) {
|
||||
nsresult rv;
|
||||
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
|
||||
if (!mAudioChannelAgent) {
|
||||
return;
|
||||
}
|
||||
mAudioChannelAgent->Init( mAudioChannelType, this);
|
||||
}
|
||||
|
||||
if (mPlayingThroughTheAudioChannel) {
|
||||
audioChannelService->RegisterMediaElement(this, mAudioChannelType);
|
||||
bool canPlay;
|
||||
mAudioChannelAgent->StartPlaying(&canPlay);
|
||||
} else {
|
||||
audioChannelService->UnregisterMediaElement(this);
|
||||
mAudioChannelAgent->StopPlaying();
|
||||
mAudioChannelAgent = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
nsresult nsHTMLMediaElement::NotifyAudioChannelStateChanged()
|
||||
/* void canPlayChanged (in boolean canPlay); */
|
||||
NS_IMETHODIMP nsHTMLMediaElement::CanPlayChanged(bool canPlay)
|
||||
{
|
||||
return UpdateChannelMuteState();
|
||||
UpdateChannelMuteState(canPlay);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -670,11 +670,7 @@ nsHTMLOptionCollection::Add(const HTMLOptionOrOptGroupElement& aElement,
|
||||
if (aBefore.IsNull()) {
|
||||
mSelect->Add(element, (nsGenericHTMLElement*)nullptr, aError);
|
||||
} else if (aBefore.Value().IsHTMLElement()) {
|
||||
nsCOMPtr<nsIContent> content =
|
||||
do_QueryInterface(aBefore.Value().GetAsHTMLElement());
|
||||
nsGenericHTMLElement* before =
|
||||
static_cast<nsGenericHTMLElement*>(content.get());
|
||||
mSelect->Add(element, before, aError);
|
||||
mSelect->Add(element, &aBefore.Value().GetAsHTMLElement(), aError);
|
||||
} else {
|
||||
mSelect->Add(element, aBefore.Value().GetAsLong(), aError);
|
||||
}
|
||||
|
@ -93,8 +93,8 @@ public:
|
||||
// held.
|
||||
virtual void UpdatePlaybackPosition(int64_t aTime) = 0;
|
||||
|
||||
// Called when the metadata from the media file has been read by the reader.
|
||||
// Call on the decode thread only.
|
||||
// May be called by the reader to notify this decoder that the metadata from
|
||||
// the media file has been read. Call on the decode thread only.
|
||||
virtual void OnReadMetadataCompleted() = 0;
|
||||
|
||||
// Stack based class to assist in notifying the frame statistics of
|
||||
|
@ -166,6 +166,8 @@ static sa_stream_type_t ConvertChannelToSAType(dom::AudioChannelType aType)
|
||||
return SA_STREAM_TYPE_ALARM;
|
||||
case dom::AUDIO_CHANNEL_TELEPHONY:
|
||||
return SA_STREAM_TYPE_VOICE_CALL;
|
||||
case dom::AUDIO_CHANNEL_RINGER:
|
||||
return SA_STREAM_TYPE_RING;
|
||||
case dom::AUDIO_CHANNEL_PUBLICNOTIFICATION:
|
||||
return SA_STREAM_TYPE_ENFORCED_AUDIBLE;
|
||||
default:
|
||||
|
144
content/media/BufferMediaResource.h
Normal file
144
content/media/BufferMediaResource.h
Normal file
@ -0,0 +1,144 @@
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
/* 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/. */
|
||||
|
||||
#if !defined(BufferMediaResource_h_)
|
||||
#define BufferMediaResource_h_
|
||||
|
||||
#include "MediaResource.h"
|
||||
#include "nsISeekableStream.h"
|
||||
#include "nsIPrincipal.h"
|
||||
#include <algorithm>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// A simple MediaResource based on an in memory buffer. This class accepts
|
||||
// the address and the length of the buffer, and simulates a read/seek API
|
||||
// on top of it. The Read implementation involves copying memory, which is
|
||||
// unfortunate, but the MediaResource interface mandates that.
|
||||
class BufferMediaResource : public MediaResource
|
||||
{
|
||||
public:
|
||||
BufferMediaResource(const uint8_t* aBuffer,
|
||||
uint32_t aLength,
|
||||
nsIPrincipal* aPrincipal) :
|
||||
mBuffer(aBuffer),
|
||||
mLength(aLength),
|
||||
mOffset(0),
|
||||
mPrincipal(aPrincipal)
|
||||
{
|
||||
MOZ_COUNT_CTOR(BufferMediaResource);
|
||||
}
|
||||
|
||||
virtual ~BufferMediaResource()
|
||||
{
|
||||
MOZ_COUNT_DTOR(BufferMediaResource);
|
||||
}
|
||||
|
||||
virtual nsresult Close() { return NS_OK; }
|
||||
virtual void Suspend(bool aCloseImmediately) {}
|
||||
virtual void Resume() {}
|
||||
// Get the current principal for the channel
|
||||
virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal()
|
||||
{
|
||||
nsCOMPtr<nsIPrincipal> principal = mPrincipal;
|
||||
return principal.forget();
|
||||
}
|
||||
virtual bool CanClone() { return false; }
|
||||
virtual MediaResource* CloneData(MediaDecoder* aDecoder)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// These methods are called off the main thread.
|
||||
// The mode is initially MODE_PLAYBACK.
|
||||
virtual void SetReadMode(MediaCacheStream::ReadMode aMode) {}
|
||||
virtual void SetPlaybackRate(uint32_t aBytesPerSecond) {}
|
||||
virtual nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes)
|
||||
{
|
||||
*aBytes = std::min(mLength - mOffset, aCount);
|
||||
memcpy(aBuffer, mBuffer + mOffset, *aBytes);
|
||||
mOffset += *aBytes;
|
||||
MOZ_ASSERT(mOffset <= mLength);
|
||||
return NS_OK;
|
||||
}
|
||||
virtual nsresult Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
MOZ_ASSERT(aOffset <= UINT32_MAX);
|
||||
switch (aWhence) {
|
||||
case nsISeekableStream::NS_SEEK_SET:
|
||||
if (aOffset < 0 || aOffset > mLength) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOffset = static_cast<uint32_t> (aOffset);
|
||||
break;
|
||||
case nsISeekableStream::NS_SEEK_CUR:
|
||||
if (aOffset >= mLength - mOffset) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOffset += static_cast<uint32_t> (aOffset);
|
||||
break;
|
||||
case nsISeekableStream::NS_SEEK_END:
|
||||
if (aOffset < 0 || aOffset > mLength) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mOffset = mLength - aOffset;
|
||||
break;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
virtual void StartSeekingForMetadata() {}
|
||||
virtual void EndSeekingForMetadata() {}
|
||||
virtual int64_t Tell() { return mOffset; }
|
||||
|
||||
virtual void Pin() {}
|
||||
virtual void Unpin() {}
|
||||
virtual double GetDownloadRate(bool* aIsReliable) { return 0.; }
|
||||
virtual int64_t GetLength() { return mLength; }
|
||||
virtual int64_t GetNextCachedData(int64_t aOffset) { return aOffset; }
|
||||
virtual int64_t GetCachedDataEnd(int64_t aOffset) { return mLength; }
|
||||
virtual bool IsDataCachedToEndOfResource(int64_t aOffset) { return true; }
|
||||
virtual bool IsSuspendedByCache(MediaResource** aActiveResource) { return false; }
|
||||
virtual bool IsSuspended() { return false; }
|
||||
virtual nsresult ReadFromCache(char* aBuffer,
|
||||
int64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
if (aOffset < 0) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
uint32_t bytes = std::min(mLength - static_cast<uint32_t>(aOffset), aCount);
|
||||
memcpy(aBuffer, mBuffer + aOffset, bytes);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
virtual nsresult Open(nsIStreamListener** aStreamListener)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
|
||||
MediaByteRange const &aByteRange)
|
||||
{
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges)
|
||||
{
|
||||
aRanges.AppendElement(MediaByteRange(0, mLength));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t * mBuffer;
|
||||
uint32_t mLength;
|
||||
uint32_t mOffset;
|
||||
nsCOMPtr<nsIPrincipal> mPrincipal;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -18,6 +18,7 @@ EXPORTS = \
|
||||
AbstractMediaDecoder.h \
|
||||
AudioSampleFormat.h \
|
||||
AudioSegment.h \
|
||||
BufferMediaResource.h \
|
||||
DecoderTraits.h \
|
||||
FileBlockCache.h \
|
||||
MediaDecoderOwner.h \
|
||||
|
@ -1719,6 +1719,50 @@ MediaCacheStream::NotifyDataReceived(int64_t aSize, const char* aData,
|
||||
mon.NotifyAll();
|
||||
}
|
||||
|
||||
void
|
||||
MediaCacheStream::FlushPartialBlockInternal(bool aNotifyAll)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
|
||||
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
|
||||
|
||||
int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
|
||||
if (blockOffset > 0) {
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("Stream %p writing partial block: [%d] bytes; "
|
||||
"mStreamOffset [%lld] mChannelOffset[%lld] mStreamLength [%lld] "
|
||||
"notifying: [%s]",
|
||||
this, blockOffset, mStreamOffset, mChannelOffset, mStreamLength,
|
||||
aNotifyAll ? "yes" : "no"));
|
||||
|
||||
// Write back the partial block
|
||||
memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
|
||||
BLOCK_SIZE - blockOffset);
|
||||
gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
|
||||
mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
|
||||
if (aNotifyAll) {
|
||||
// Wake up readers who may be waiting for this data
|
||||
mon.NotifyAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaCacheStream::FlushPartialBlock()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
|
||||
|
||||
ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
|
||||
|
||||
// Write the current partial block to memory.
|
||||
// Note: This writes a full block, so if data is not at the end of the
|
||||
// stream, the decoder must subsequently choose correct start and end offsets
|
||||
// for reading/seeking.
|
||||
FlushPartialBlockInternal(false);
|
||||
|
||||
gMediaCache->QueueUpdate();
|
||||
}
|
||||
|
||||
void
|
||||
MediaCacheStream::NotifyDataEnded(nsresult aStatus)
|
||||
{
|
||||
@ -1733,16 +1777,7 @@ MediaCacheStream::NotifyDataEnded(nsresult aStatus)
|
||||
mResourceID = gMediaCache->AllocateResourceID();
|
||||
}
|
||||
|
||||
int32_t blockOffset = int32_t(mChannelOffset%BLOCK_SIZE);
|
||||
if (blockOffset > 0) {
|
||||
// Write back the partial block
|
||||
memset(reinterpret_cast<char*>(mPartialBlockBuffer) + blockOffset, 0,
|
||||
BLOCK_SIZE - blockOffset);
|
||||
gMediaCache->AllocateAndWriteBlock(this, mPartialBlockBuffer,
|
||||
mMetadataInPartialBlockBuffer ? MODE_METADATA : MODE_PLAYBACK);
|
||||
// Wake up readers who may be waiting for this data
|
||||
mon.NotifyAll();
|
||||
}
|
||||
FlushPartialBlockInternal(true);
|
||||
|
||||
if (!mDidNotifyDataEnded) {
|
||||
MediaCache::ResourceStreamIterator iter(mResourceID);
|
||||
|
@ -273,6 +273,9 @@ public:
|
||||
// We pass in the principal that was used to load this data.
|
||||
void NotifyDataReceived(int64_t aSize, const char* aData,
|
||||
nsIPrincipal* aPrincipal);
|
||||
// Notifies the cache that the current bytes should be written to disk.
|
||||
// Called on the main thread.
|
||||
void FlushPartialBlock();
|
||||
// Notifies the cache that the channel has closed with the given status.
|
||||
void NotifyDataEnded(nsresult aStatus);
|
||||
|
||||
@ -415,6 +418,11 @@ private:
|
||||
// This method assumes that the cache monitor is held and can be called on
|
||||
// any thread.
|
||||
int64_t GetNextCachedDataInternal(int64_t aOffset);
|
||||
// Writes |mPartialBlock| to disk.
|
||||
// Used by |NotifyDataEnded| and |FlushPartialBlock|.
|
||||
// If |aNotifyAll| is true, this function will wake up readers who may be
|
||||
// waiting on the media cache monitor. Called on the main thread only.
|
||||
void FlushPartialBlockInternal(bool aNotify);
|
||||
// A helper function to do the work of closing the stream. Assumes
|
||||
// that the cache monitor is held. Main thread only.
|
||||
// aReentrantMonitor is the nsAutoReentrantMonitor wrapper holding the cache monitor.
|
||||
|
@ -655,8 +655,8 @@ public:
|
||||
// change. Call on the main thread only.
|
||||
void ChangeState(PlayState aState);
|
||||
|
||||
// Called when the metadata from the media file has been read by the reader.
|
||||
// Call on the decode thread only.
|
||||
// May be called by the reader to notify this decoder that the metadata from
|
||||
// the media file has been read. Call on the decode thread only.
|
||||
void OnReadMetadataCompleted() MOZ_OVERRIDE { }
|
||||
|
||||
// Called when the metadata from the media file has been loaded by the
|
||||
|
@ -337,8 +337,8 @@ nsresult MediaDecoderReader::ResetDecode()
|
||||
{
|
||||
nsresult res = NS_OK;
|
||||
|
||||
mVideoQueue.Reset();
|
||||
mAudioQueue.Reset();
|
||||
VideoQueue().Reset();
|
||||
AudioQueue().Reset();
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -346,7 +346,7 @@ nsresult MediaDecoderReader::ResetDecode()
|
||||
VideoData* MediaDecoderReader::DecodeToFirstVideoData()
|
||||
{
|
||||
bool eof = false;
|
||||
while (!eof && mVideoQueue.GetSize() == 0) {
|
||||
while (!eof && VideoQueue().GetSize() == 0) {
|
||||
{
|
||||
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
|
||||
if (mDecoder->IsShutdown()) {
|
||||
@ -357,13 +357,13 @@ VideoData* MediaDecoderReader::DecodeToFirstVideoData()
|
||||
eof = !DecodeVideoFrame(keyframeSkip, 0);
|
||||
}
|
||||
VideoData* d = nullptr;
|
||||
return (d = mVideoQueue.PeekFront()) ? d : nullptr;
|
||||
return (d = VideoQueue().PeekFront()) ? d : nullptr;
|
||||
}
|
||||
|
||||
AudioData* MediaDecoderReader::DecodeToFirstAudioData()
|
||||
{
|
||||
bool eof = false;
|
||||
while (!eof && mAudioQueue.GetSize() == 0) {
|
||||
while (!eof && AudioQueue().GetSize() == 0) {
|
||||
{
|
||||
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
|
||||
if (mDecoder->IsShutdown()) {
|
||||
@ -373,7 +373,7 @@ AudioData* MediaDecoderReader::DecodeToFirstAudioData()
|
||||
eof = !DecodeAudioData();
|
||||
}
|
||||
AudioData* d = nullptr;
|
||||
return (d = mAudioQueue.PeekFront()) ? d : nullptr;
|
||||
return (d = AudioQueue().PeekFront()) ? d : nullptr;
|
||||
}
|
||||
|
||||
VideoData* MediaDecoderReader::FindStartTime(int64_t& aOutStartTime)
|
||||
@ -416,7 +416,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
int64_t startTime = -1;
|
||||
nsAutoPtr<VideoData> video;
|
||||
while (HasVideo() && !eof) {
|
||||
while (mVideoQueue.GetSize() == 0 && !eof) {
|
||||
while (VideoQueue().GetSize() == 0 && !eof) {
|
||||
bool skip = false;
|
||||
eof = !DecodeVideoFrame(skip, 0);
|
||||
{
|
||||
@ -426,21 +426,21 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mVideoQueue.GetSize() == 0) {
|
||||
if (VideoQueue().GetSize() == 0) {
|
||||
// Hit end of file, we want to display the last frame of the video.
|
||||
if (video) {
|
||||
mVideoQueue.PushFront(video.forget());
|
||||
VideoQueue().PushFront(video.forget());
|
||||
}
|
||||
break;
|
||||
}
|
||||
video = mVideoQueue.PeekFront();
|
||||
video = VideoQueue().PeekFront();
|
||||
// If the frame end time is less than the seek target, we won't want
|
||||
// to display this frame after the seek, so discard it.
|
||||
if (video && video->mEndTime <= aTarget) {
|
||||
if (startTime == -1) {
|
||||
startTime = video->mTime;
|
||||
}
|
||||
mVideoQueue.PopFront();
|
||||
VideoQueue().PopFront();
|
||||
} else {
|
||||
video.forget();
|
||||
break;
|
||||
@ -459,7 +459,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
// Decode audio forward to the seek target.
|
||||
bool eof = false;
|
||||
while (HasAudio() && !eof) {
|
||||
while (!eof && mAudioQueue.GetSize() == 0) {
|
||||
while (!eof && AudioQueue().GetSize() == 0) {
|
||||
eof = !DecodeAudioData();
|
||||
{
|
||||
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
|
||||
@ -468,7 +468,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
}
|
||||
}
|
||||
}
|
||||
const AudioData* audio = mAudioQueue.PeekFront();
|
||||
const AudioData* audio = AudioQueue().PeekFront();
|
||||
if (!audio)
|
||||
break;
|
||||
CheckedInt64 startFrame = UsecsToFrames(audio->mTime, mInfo.mAudioRate);
|
||||
@ -479,7 +479,7 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
if (startFrame.value() + audio->mFrames <= targetFrame.value()) {
|
||||
// Our seek target lies after the frames in this AudioData. Pop it
|
||||
// off the queue, and keep decoding forwards.
|
||||
delete mAudioQueue.PopFront();
|
||||
delete AudioQueue().PopFront();
|
||||
audio = nullptr;
|
||||
continue;
|
||||
}
|
||||
@ -526,8 +526,8 @@ nsresult MediaDecoderReader::DecodeToTarget(int64_t aTarget)
|
||||
frames,
|
||||
audioData.forget(),
|
||||
channels));
|
||||
delete mAudioQueue.PopFront();
|
||||
mAudioQueue.PushFront(data.forget());
|
||||
delete AudioQueue().PopFront();
|
||||
AudioQueue().PushFront(data.forget());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -373,6 +373,9 @@ public:
|
||||
// or an un-recoverable read error has occured.
|
||||
virtual bool DecodeAudioData() = 0;
|
||||
|
||||
// Steps to carry out at the start of the |DecodeLoop|.
|
||||
virtual void PrepareToDecode() { }
|
||||
|
||||
// Reads and decodes one video frame. Packets with a timestamp less
|
||||
// than aTimeThreshold will be decoded (unless they're not keyframes
|
||||
// and aKeyframeSkip is true), but will not be added to the queue.
|
||||
@ -471,17 +474,6 @@ public:
|
||||
AudioData* DecodeToFirstAudioData();
|
||||
VideoData* DecodeToFirstVideoData();
|
||||
|
||||
// Sets range for initialization bytes; used by DASH.
|
||||
virtual void SetInitByteRange(MediaByteRange &aByteRange) { }
|
||||
|
||||
// Sets range for index frame bytes; used by DASH.
|
||||
virtual void SetIndexByteRange(MediaByteRange &aByteRange) { }
|
||||
|
||||
// Returns list of ranges for index frame start/end offsets. Used by DASH.
|
||||
virtual nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
protected:
|
||||
// Pumps the decode until we reach frames required to play at time aTarget
|
||||
// (usecs).
|
||||
|
@ -793,6 +793,8 @@ void MediaDecoderStateMachine::DecodeLoop()
|
||||
!mStopDecodeThread &&
|
||||
(videoPlaying || audioPlaying))
|
||||
{
|
||||
mReader->PrepareToDecode();
|
||||
|
||||
// We don't want to consider skipping to the next keyframe if we've
|
||||
// only just started up the decode loop, so wait until we've decoded
|
||||
// some frames before enabling the keyframe skip logic on video.
|
||||
|
@ -234,7 +234,8 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
|
||||
// accept ranges.
|
||||
if (!acceptsRanges) {
|
||||
CMLOG("Error! HTTP_PARTIAL_RESPONSE_CODE received but server says "
|
||||
"range requests are not accepted! Channel[%p]", hc.get());
|
||||
"range requests are not accepted! Channel[%p] decoder[%p]",
|
||||
hc.get(), mDecoder);
|
||||
mDecoder->NetworkError();
|
||||
CloseChannel();
|
||||
return NS_OK;
|
||||
@ -248,7 +249,8 @@ ChannelMediaResource::OnStartRequest(nsIRequest* aRequest)
|
||||
if (NS_FAILED(rv)) {
|
||||
// Content-Range header text should be parse-able.
|
||||
CMLOG("Error processing \'Content-Range' for "
|
||||
"HTTP_PARTIAL_RESPONSE_CODE: rv[%x]channel [%p]", rv, hc.get());
|
||||
"HTTP_PARTIAL_RESPONSE_CODE: rv[%x] channel[%p] decoder[%p]",
|
||||
rv, hc.get(), mDecoder);
|
||||
mDecoder->NetworkError();
|
||||
CloseChannel();
|
||||
return NS_OK;
|
||||
@ -389,8 +391,8 @@ ChannelMediaResource::ParseContentRangeHeader(nsIHttpChannel * aHttpChan,
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
CMLOG("Received bytes [%d] to [%d] of [%d]",
|
||||
aRangeStart, aRangeEnd, aRangeTotal);
|
||||
CMLOG("Received bytes [%lld] to [%lld] of [%lld] for decoder[%p]",
|
||||
aRangeStart, aRangeEnd, aRangeTotal, mDecoder);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -478,10 +480,23 @@ ChannelMediaResource::CopySegmentToCache(nsIInputStream *aInStream,
|
||||
|
||||
closure->mResource->mDecoder->NotifyDataArrived(aFromSegment, aCount, closure->mResource->mOffset);
|
||||
|
||||
// Keep track of where we're up to
|
||||
// For byte range downloads controlled by |DASHDecoder|, there are cases in
|
||||
// which the reader's offset is different enough from the channel offset that
|
||||
// |MediaCache| requests a |CacheClientSeek| to the reader's offset. This
|
||||
// can happen between calls to |CopySegmentToCache|. To avoid copying at
|
||||
// incorrect offsets, ensure |MediaCache| copies to the location that
|
||||
// |ChannelMediaResource| expects.
|
||||
if (closure->mResource->mByteRangeDownloads) {
|
||||
closure->mResource->mCacheStream.NotifyDataStarted(closure->mResource->mOffset);
|
||||
}
|
||||
|
||||
// Keep track of where we're up to.
|
||||
LOG("%p [ChannelMediaResource]: CopySegmentToCache at mOffset [%lld] add "
|
||||
"[%d] bytes for decoder[%p]",
|
||||
closure->mResource, closure->mResource->mOffset, aCount,
|
||||
closure->mResource->mDecoder);
|
||||
closure->mResource->mOffset += aCount;
|
||||
LOG("%p [ChannelMediaResource]: CopySegmentToCache new mOffset = %d",
|
||||
closure->mResource, closure->mResource->mOffset);
|
||||
|
||||
closure->mResource->mCacheStream.NotifyDataReceived(aCount, aFromSegment,
|
||||
closure->mPrincipal);
|
||||
*aWriteCount = aCount;
|
||||
@ -760,6 +775,8 @@ nsresult ChannelMediaResource::Seek(int32_t aWhence, int64_t aOffset)
|
||||
{
|
||||
NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
|
||||
|
||||
CMLOG("Seek requested for aOffset [%lld] for decoder [%p]",
|
||||
aOffset, mDecoder);
|
||||
// Remember |aOffset|, because Media Cache may request a diff offset later.
|
||||
if (mByteRangeDownloads) {
|
||||
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
|
||||
@ -971,7 +988,21 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
|
||||
|
||||
CloseChannel();
|
||||
CMLOG("CacheClientSeek requested for aOffset [%lld] for decoder [%p]",
|
||||
aOffset, mDecoder);
|
||||
|
||||
// |CloseChannel| immediately for non-byte-range downloads.
|
||||
if (!mByteRangeDownloads) {
|
||||
CloseChannel();
|
||||
} else if (mChannel) {
|
||||
// Only close byte range channels if they are not in pending state.
|
||||
bool isPending = false;
|
||||
nsresult rv = mChannel->IsPending(&isPending);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!isPending) {
|
||||
CloseChannel();
|
||||
}
|
||||
}
|
||||
|
||||
if (aResume) {
|
||||
NS_ASSERTION(mSuspendCount > 0, "Too many resumes!");
|
||||
@ -1001,24 +1032,55 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
|
||||
nsresult rv;
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mSeekOffsetMonitor);
|
||||
// Ensure that media cache can only request an equal or smaller offset;
|
||||
// it may be trying to include the start of a cache block.
|
||||
NS_ENSURE_TRUE(aOffset <= mSeekOffset, NS_ERROR_ILLEGAL_VALUE);
|
||||
rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
|
||||
mSeekOffset = -1;
|
||||
// Only continue with seek request if a prior call to |Seek| was made.
|
||||
// If |Seek| was not called previously, it means the media cache is
|
||||
// seeking on its own.
|
||||
// E.g. For those WebM files which are encoded with cues at the end of
|
||||
// the file, when the cues are parsed, the reader and media cache
|
||||
// automatically return to the first offset not downloaded, normally the
|
||||
// first byte after init data. This results in |MediaCache| requesting
|
||||
// |aOffset| = 0 (aligning to the start of the cache block. Ignore this
|
||||
// and let |DASHDecoder| decide which bytes to download and when.
|
||||
if (mSeekOffset >= 0) {
|
||||
rv = mDecoder->GetByteRangeForSeek(mSeekOffset, mByteRange);
|
||||
// Cache may try to seek from the next uncached byte: this offset may
|
||||
// be after the byte range being seeked, i.e. the range containing
|
||||
// |mSeekOffset|, which is the offset actually requested by the reader.
|
||||
// This case means that the seeked range is already cached. For byte
|
||||
// range downloads, we do not permit the cache to request bytes outside
|
||||
// the seeked range. Instead, the decoder is responsible for
|
||||
// controlling the sequence of byte range downloads. As such, return
|
||||
// silently, and do NOT request a new download.
|
||||
if (NS_SUCCEEDED(rv) && !mByteRange.IsNull() &&
|
||||
aOffset > mByteRange.mEnd) {
|
||||
rv = NS_ERROR_NOT_AVAILABLE;
|
||||
mByteRange.Clear();
|
||||
}
|
||||
mSeekOffset = -1;
|
||||
} else {
|
||||
LOG("MediaCache [%p] trying to seek independently to offset [%lld].",
|
||||
&mCacheStream, aOffset);
|
||||
rv = NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
}
|
||||
if (rv == NS_ERROR_NOT_AVAILABLE) {
|
||||
// Assume decoder will request correct bytes when range information
|
||||
// becomes available. Return silently.
|
||||
// Decoder will not make byte ranges available for non-active streams, or
|
||||
// if range information is not yet available, or for metadata bytes if
|
||||
// they have already been downloaded and read. In all cases, it is ok to
|
||||
// return silently and assume that the decoder will request the correct
|
||||
// byte range when range information becomes available.
|
||||
CMLOG("Byte range not available for decoder [%p]; returning "
|
||||
"silently.", mDecoder);
|
||||
return NS_OK;
|
||||
} else if (NS_FAILED(rv) || mByteRange.IsNull()) {
|
||||
// Decoder reported an error we don't want to handle here; just return.
|
||||
CMLOG("Error getting byte range: seek offset[%lld] cache offset[%lld] "
|
||||
"decoder[%p]", mSeekOffset, aOffset, mDecoder);
|
||||
mDecoder->NetworkError();
|
||||
CloseChannel();
|
||||
return rv;
|
||||
}
|
||||
// Media cache may decrease offset to start of cache data block.
|
||||
// Adjust start of byte range accordingly.
|
||||
// Adjust the byte range to start where the media cache requested.
|
||||
mByteRange.mStart = mOffset = aOffset;
|
||||
return OpenByteRange(nullptr, mByteRange);
|
||||
}
|
||||
@ -1042,6 +1104,26 @@ ChannelMediaResource::CacheClientSeek(int64_t aOffset, bool aResume)
|
||||
return OpenChannel(nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
ChannelMediaResource::FlushCache()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Ensure that data in the cache's partial block is written to disk.
|
||||
mCacheStream.FlushPartialBlock();
|
||||
}
|
||||
|
||||
void
|
||||
ChannelMediaResource::NotifyLastByteRange()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Tell media cache that the last data has been downloaded.
|
||||
// Note: subsequent seeks will require re-opening the channel etc.
|
||||
mCacheStream.NotifyDataEnded(NS_OK);
|
||||
|
||||
}
|
||||
|
||||
nsresult
|
||||
ChannelMediaResource::CacheClientSuspend()
|
||||
{
|
||||
|
@ -304,6 +304,13 @@ public:
|
||||
* aRanges is being used.
|
||||
*/
|
||||
virtual nsresult GetCachedRanges(nsTArray<MediaByteRange>& aRanges) = 0;
|
||||
|
||||
// Ensure that the media cache writes any data held in its partial block.
|
||||
// Called on the main thread only.
|
||||
virtual void FlushCache() { }
|
||||
|
||||
// Notify that the last data byte range was loaded.
|
||||
virtual void NotifyLastByteRange() { }
|
||||
};
|
||||
|
||||
class BaseMediaResource : public MediaResource {
|
||||
@ -388,6 +395,13 @@ public:
|
||||
// Resume the current load since data is wanted again
|
||||
nsresult CacheClientResume();
|
||||
|
||||
// Ensure that the media cache writes any data held in its partial block.
|
||||
// Called on the main thread.
|
||||
virtual void FlushCache() MOZ_OVERRIDE;
|
||||
|
||||
// Notify that the last data byte range was loaded.
|
||||
virtual void NotifyLastByteRange() MOZ_OVERRIDE;
|
||||
|
||||
// Main thread
|
||||
virtual nsresult Open(nsIStreamListener** aStreamListener);
|
||||
virtual nsresult OpenByteRange(nsIStreamListener** aStreamListener,
|
||||
|
@ -70,6 +70,45 @@ private:
|
||||
ReentrantMonitor* mReentrantMonitor;
|
||||
};
|
||||
|
||||
/**
|
||||
* ReentrantMonitorConditionallyEnter
|
||||
*
|
||||
* Enters the supplied monitor only if the conditional value |aEnter| is true.
|
||||
* E.g. Used to allow unmonitored read access on the decode thread,
|
||||
* and monitored access on all other threads.
|
||||
*/
|
||||
class NS_STACK_CLASS ReentrantMonitorConditionallyEnter
|
||||
{
|
||||
public:
|
||||
ReentrantMonitorConditionallyEnter(bool aEnter,
|
||||
ReentrantMonitor &aReentrantMonitor) :
|
||||
mReentrantMonitor(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(ReentrantMonitorConditionallyEnter);
|
||||
if (aEnter) {
|
||||
mReentrantMonitor = &aReentrantMonitor;
|
||||
NS_ASSERTION(mReentrantMonitor, "null monitor");
|
||||
mReentrantMonitor->Enter();
|
||||
}
|
||||
}
|
||||
~ReentrantMonitorConditionallyEnter(void)
|
||||
{
|
||||
if (mReentrantMonitor) {
|
||||
mReentrantMonitor->Exit();
|
||||
}
|
||||
MOZ_COUNT_DTOR(ReentrantMonitorConditionallyEnter);
|
||||
}
|
||||
private:
|
||||
// Restrict to constructor and destructor defined above.
|
||||
ReentrantMonitorConditionallyEnter();
|
||||
ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
|
||||
ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
|
||||
static void* operator new(size_t) CPP_THROW_NEW;
|
||||
static void operator delete(void*);
|
||||
|
||||
ReentrantMonitor* mReentrantMonitor;
|
||||
};
|
||||
|
||||
// Shuts down a thread asynchronously.
|
||||
class ShutdownThreadEvent : public nsRunnable
|
||||
{
|
||||
|
@ -149,8 +149,13 @@ DASHDecoder::DASHDecoder() :
|
||||
mMPDReaderThread(nullptr),
|
||||
mPrincipal(nullptr),
|
||||
mDASHReader(nullptr),
|
||||
mAudioRepDecoder(nullptr),
|
||||
mVideoRepDecoder(nullptr)
|
||||
mVideoAdaptSetIdx(-1),
|
||||
mAudioRepDecoderIdx(-1),
|
||||
mVideoRepDecoderIdx(-1),
|
||||
mAudioSubsegmentIdx(0),
|
||||
mVideoSubsegmentIdx(0),
|
||||
mAudioMetadataReadCount(0),
|
||||
mVideoMetadataReadCount(0)
|
||||
{
|
||||
MOZ_COUNT_CTOR(DASHDecoder);
|
||||
}
|
||||
@ -297,6 +302,7 @@ DASHDecoder::OnReadMPDBufferCompleted()
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
if (mShuttingDown) {
|
||||
LOG1("Shutting down! Ignoring OnReadMPDBufferCompleted().");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -385,6 +391,9 @@ DASHDecoder::CreateRepDecoders()
|
||||
|
||||
for (int i = 0; i < mMPDManager->GetNumAdaptationSets(); i++) {
|
||||
IMPDManager::AdaptationSetType asType = mMPDManager->GetAdaptationSetType(i);
|
||||
if (asType == IMPDManager::DASH_VIDEO_STREAM) {
|
||||
mVideoAdaptSetIdx = i;
|
||||
}
|
||||
for (int j = 0; j < mMPDManager->GetNumRepresentations(i); j++) {
|
||||
// Get URL string.
|
||||
nsAutoString segmentUrl;
|
||||
@ -422,8 +431,8 @@ DASHDecoder::CreateRepDecoders()
|
||||
}
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(mVideoRepDecoder, NS_ERROR_NOT_INITIALIZED);
|
||||
NS_ENSURE_TRUE(mAudioRepDecoder, NS_ERROR_NOT_INITIALIZED);
|
||||
NS_ENSURE_TRUE(VideoRepDecoder(), NS_ERROR_NOT_INITIALIZED);
|
||||
NS_ENSURE_TRUE(AudioRepDecoder(), NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -441,14 +450,16 @@ DASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
|
||||
DASHRepDecoder* audioDecoder = new DASHRepDecoder(this);
|
||||
NS_ENSURE_TRUE(audioDecoder->Init(mOwner), NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
if (!mAudioRepDecoder) {
|
||||
mAudioRepDecoder = audioDecoder;
|
||||
// Set current decoder to the first one created.
|
||||
if (mAudioRepDecoderIdx == -1) {
|
||||
mAudioRepDecoderIdx = 0;
|
||||
}
|
||||
mAudioRepDecoders.AppendElement(audioDecoder);
|
||||
|
||||
// Create sub-reader; attach to DASH reader and sub-decoder.
|
||||
WebMReader* audioReader = new WebMReader(audioDecoder);
|
||||
if (mDASHReader) {
|
||||
audioReader->SetMainReader(mDASHReader);
|
||||
mDASHReader->AddAudioReader(audioReader);
|
||||
}
|
||||
audioDecoder->SetReader(audioReader);
|
||||
@ -461,6 +472,8 @@ DASHDecoder::CreateAudioRepDecoder(nsIURI* aUrl,
|
||||
audioDecoder->SetResource(audioResource);
|
||||
audioDecoder->SetMPDRepresentation(aRep);
|
||||
|
||||
LOG("Created audio DASHRepDecoder [%p]", audioDecoder);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -477,14 +490,16 @@ DASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
|
||||
DASHRepDecoder* videoDecoder = new DASHRepDecoder(this);
|
||||
NS_ENSURE_TRUE(videoDecoder->Init(mOwner), NS_ERROR_NOT_INITIALIZED);
|
||||
|
||||
if (!mVideoRepDecoder) {
|
||||
mVideoRepDecoder = videoDecoder;
|
||||
// Set current decoder to the first one created.
|
||||
if (mVideoRepDecoderIdx == -1) {
|
||||
mVideoRepDecoderIdx = 0;
|
||||
}
|
||||
mVideoRepDecoders.AppendElement(videoDecoder);
|
||||
|
||||
// Create sub-reader; attach to DASH reader and sub-decoder.
|
||||
WebMReader* videoReader = new WebMReader(videoDecoder);
|
||||
if (mDASHReader) {
|
||||
videoReader->SetMainReader(mDASHReader);
|
||||
mDASHReader->AddVideoReader(videoReader);
|
||||
}
|
||||
videoDecoder->SetReader(videoReader);
|
||||
@ -497,6 +512,8 @@ DASHDecoder::CreateVideoRepDecoder(nsIURI* aUrl,
|
||||
videoDecoder->SetResource(videoResource);
|
||||
videoDecoder->SetMPDRepresentation(aRep);
|
||||
|
||||
LOG("Created video DASHRepDecoder [%p]", videoDecoder);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -595,38 +612,42 @@ DASHDecoder::LoadRepresentations()
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
// Load the decoders for each |Representation|'s media streams.
|
||||
if (mAudioRepDecoder) {
|
||||
rv = mAudioRepDecoder->Load();
|
||||
// XXX Prob ok to load all audio decoders, since there should only be one
|
||||
// created, but need to review the rest of the file.
|
||||
if (AudioRepDecoder()) {
|
||||
rv = AudioRepDecoder()->Load();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mAudioMetadataReadCount++;
|
||||
}
|
||||
if (mVideoRepDecoder) {
|
||||
rv = mVideoRepDecoder->Load();
|
||||
// Load all video decoders.
|
||||
for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
|
||||
rv = mVideoRepDecoders[i]->Load();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mVideoMetadataReadCount++;
|
||||
}
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Failed to open stream! rv [%x].", rv);
|
||||
return rv;
|
||||
if (AudioRepDecoder()) {
|
||||
AudioRepDecoder()->SetStateMachine(mDecoderStateMachine);
|
||||
}
|
||||
for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
|
||||
mVideoRepDecoders[i]->SetStateMachine(mDecoderStateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
if (mAudioRepDecoder) {
|
||||
mAudioRepDecoder->SetStateMachine(mDecoderStateMachine);
|
||||
}
|
||||
if (mVideoRepDecoder) {
|
||||
mVideoRepDecoder->SetStateMachine(mDecoderStateMachine);
|
||||
}
|
||||
|
||||
// Now that subreaders are init'd, it's ok to init state machine.
|
||||
return InitializeStateMachine(nullptr);
|
||||
}
|
||||
|
||||
void
|
||||
DASHDecoder::NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
|
||||
nsresult aStatus,
|
||||
MediaByteRange &aRange)
|
||||
nsresult aStatus,
|
||||
int32_t const aSubsegmentIdx)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
if (mShuttingDown) {
|
||||
LOG1("Shutting down! Ignoring NotifyDownloadEnded().");
|
||||
return;
|
||||
}
|
||||
|
||||
// MPD Manager must exist, indicating MPD has been downloaded and parsed.
|
||||
if (!mMPDManager) {
|
||||
LOG1("Network Error! MPD Manager must exist, indicating MPD has been "
|
||||
@ -643,21 +664,92 @@ DASHDecoder::NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
|
||||
}
|
||||
|
||||
if (NS_SUCCEEDED(aStatus)) {
|
||||
// Return error if |aRepDecoder| does not match current audio/video decoder.
|
||||
if (aRepDecoder != mAudioRepDecoder && aRepDecoder != mVideoRepDecoder) {
|
||||
LOG("Error! Decoder [%p] does not match current sub-decoders!",
|
||||
LOG("Byte range downloaded: decoder [%p] subsegmentIdx [%d]",
|
||||
aRepDecoder, aSubsegmentIdx);
|
||||
|
||||
if (aSubsegmentIdx < 0) {
|
||||
LOG("Last subsegment for decoder [%p] was downloaded",
|
||||
aRepDecoder);
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<DASHRepDecoder> decoder = aRepDecoder;
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
if (!IsDecoderAllowedToDownloadSubsegment(aRepDecoder,
|
||||
aSubsegmentIdx)) {
|
||||
NS_WARNING("Decoder downloaded subsegment but it is not allowed!");
|
||||
LOG("Error! Decoder [%p] downloaded subsegment [%d] but it is not "
|
||||
"allowed!", aRepDecoder, aSubsegmentIdx);
|
||||
return;
|
||||
}
|
||||
|
||||
if (aRepDecoder == VideoRepDecoder() &&
|
||||
mVideoSubsegmentIdx == aSubsegmentIdx) {
|
||||
IncrementSubsegmentIndex(aRepDecoder);
|
||||
} else if (aRepDecoder == AudioRepDecoder() &&
|
||||
mAudioSubsegmentIdx == aSubsegmentIdx) {
|
||||
IncrementSubsegmentIndex(aRepDecoder);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do Stream Switching here before loading next bytes.
|
||||
// Audio stream switching not supported.
|
||||
if (aRepDecoder == VideoRepDecoder() &&
|
||||
mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges()) {
|
||||
nsresult rv = PossiblySwitchDecoder(aRepDecoder);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Failed possibly switching decoder rv[0x%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
decoder = VideoRepDecoder();
|
||||
}
|
||||
}
|
||||
|
||||
// Before loading, note the index of the decoder which will downloaded the
|
||||
// next video subsegment.
|
||||
if (decoder == VideoRepDecoder()) {
|
||||
if (mVideoSubsegmentLoads.IsEmpty() ||
|
||||
(uint32_t)mVideoSubsegmentIdx >= mVideoSubsegmentLoads.Length()) {
|
||||
LOG("Appending decoder [%d] [%p] to mVideoSubsegmentLoads at index "
|
||||
"[%d] before load; mVideoSubsegmentIdx[%d].",
|
||||
mVideoRepDecoderIdx, VideoRepDecoder(),
|
||||
mVideoSubsegmentLoads.Length(), mVideoSubsegmentIdx);
|
||||
mVideoSubsegmentLoads.AppendElement(mVideoRepDecoderIdx);
|
||||
} else {
|
||||
// Change an existing load, and keep subsequent entries to help
|
||||
// determine if subsegments are cached already.
|
||||
LOG("Setting decoder [%d] [%p] in mVideoSubsegmentLoads at index "
|
||||
"[%d] before load; mVideoSubsegmentIdx[%d].",
|
||||
mVideoRepDecoderIdx, VideoRepDecoder(),
|
||||
mVideoSubsegmentIdx, mVideoSubsegmentIdx);
|
||||
mVideoSubsegmentLoads[mVideoSubsegmentIdx] = mVideoRepDecoderIdx;
|
||||
}
|
||||
}
|
||||
|
||||
// Load the next range of data bytes. If the range is already cached,
|
||||
// this function will be called again to adaptively download the next
|
||||
// subsegment.
|
||||
#ifdef PR_LOGGING
|
||||
if (decoder.get() == AudioRepDecoder()) {
|
||||
LOG("Requesting load for audio decoder [%p] subsegment [%d].",
|
||||
decoder.get(), mAudioSubsegmentIdx);
|
||||
} else if (decoder.get() == VideoRepDecoder()) {
|
||||
LOG("Requesting load for video decoder [%p] subsegment [%d].",
|
||||
decoder.get(), mVideoSubsegmentIdx);
|
||||
}
|
||||
#endif
|
||||
if (!decoder || (decoder != AudioRepDecoder() &&
|
||||
decoder != VideoRepDecoder())) {
|
||||
LOG("Invalid decoder [%p]: video idx [%d] audio idx [%d]",
|
||||
decoder.get(), AudioRepDecoder(), VideoRepDecoder());
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
LOG("Byte range downloaded: decoder [%p] range requested [%d - %d]",
|
||||
aRepDecoder, aRange.mStart, aRange.mEnd);
|
||||
|
||||
// XXX Do Stream Switching here before loading next bytes, e.g.
|
||||
// decoder = PossiblySwitchDecoder(aRepDecoder);
|
||||
// decoder->LoadNextByteRange();
|
||||
aRepDecoder->LoadNextByteRange();
|
||||
return;
|
||||
decoder->LoadNextByteRange();
|
||||
} else if (aStatus == NS_BINDING_ABORTED) {
|
||||
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
|
||||
if (mOwner) {
|
||||
@ -731,5 +823,176 @@ DASHDecoder::DecodeError()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DASHDecoder::OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder)
|
||||
{
|
||||
if (mShuttingDown) {
|
||||
LOG1("Shutting down! Ignoring OnReadMetadataCompleted().");
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(aRepDecoder, "aRepDecoder is null!");
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
LOG("Metadata loaded for decoder[%p]", aRepDecoder);
|
||||
|
||||
// Decrement audio|video metadata read counter and get ref to active decoder.
|
||||
nsRefPtr<DASHRepDecoder> activeDecoder;
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
for (uint32_t i = 0; i < mAudioRepDecoders.Length(); i++) {
|
||||
if (aRepDecoder == mAudioRepDecoders[i]) {
|
||||
--mAudioMetadataReadCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < mVideoRepDecoders.Length(); i++) {
|
||||
if (aRepDecoder == mVideoRepDecoders[i]) {
|
||||
--mVideoMetadataReadCount;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Once all metadata is downloaded for audio|video decoders, start loading
|
||||
// data for the active decoder.
|
||||
if (mAudioMetadataReadCount == 0 && mVideoMetadataReadCount == 0) {
|
||||
if (AudioRepDecoder()) {
|
||||
LOG("Dispatching load event for audio decoder [%p]", AudioRepDecoder());
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(AudioRepDecoder(), &DASHRepDecoder::LoadNextByteRange);
|
||||
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching audio decoder [%p] load event to main thread: "
|
||||
"rv[%x]", AudioRepDecoder(), rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (VideoRepDecoder()) {
|
||||
LOG("Dispatching load event for video decoder [%p]", VideoRepDecoder());
|
||||
// Add decoder to subsegment load history.
|
||||
NS_ASSERTION(mVideoSubsegmentLoads.IsEmpty(),
|
||||
"No subsegment loads should be recorded at this stage!");
|
||||
NS_ASSERTION(mVideoSubsegmentIdx == 0,
|
||||
"Current subsegment should be 0 at this stage!");
|
||||
LOG("Appending decoder [%d] [%p] to mVideoSubsegmentLoads at index "
|
||||
"[%d] before load; mVideoSubsegmentIdx[%d].",
|
||||
mVideoRepDecoderIdx, VideoRepDecoder(),
|
||||
(uint32_t)mVideoSubsegmentLoads.Length(), mVideoSubsegmentIdx);
|
||||
mVideoSubsegmentLoads.AppendElement(mVideoRepDecoderIdx);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(VideoRepDecoder(), &DASHRepDecoder::LoadNextByteRange);
|
||||
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching video decoder [%p] load event to main thread: "
|
||||
"rv[%x]", VideoRepDecoder(), rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
DASHDecoder::PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
|
||||
NS_ENSURE_TRUE(aRepDecoder == VideoRepDecoder(), NS_ERROR_ILLEGAL_VALUE);
|
||||
NS_ASSERTION((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
|
||||
"Index for video decoder is out of bounds!");
|
||||
NS_ASSERTION((uint32_t)mVideoSubsegmentIdx < VideoRepDecoder()->GetNumDataByteRanges(),
|
||||
"Can't switch to a byte range out of bounds.");
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
// Now, determine if and which decoder to switch to.
|
||||
// XXX This download rate is averaged over time, and only refers to the bytes
|
||||
// downloaded for the given decoder. A running average would be better, and
|
||||
// something that includes all downloads. But this will do for now.
|
||||
NS_ASSERTION(VideoRepDecoder(), "Video decoder should not be null.");
|
||||
NS_ASSERTION(VideoRepDecoder()->GetResource(),
|
||||
"Video resource should not be null");
|
||||
bool reliable = false;
|
||||
double downloadRate = VideoRepDecoder()->GetResource()->GetDownloadRate(&reliable);
|
||||
uint32_t bestRepIdx = UINT32_MAX;
|
||||
bool noRepAvailable = !mMPDManager->GetBestRepForBandwidth(mVideoAdaptSetIdx,
|
||||
downloadRate,
|
||||
bestRepIdx);
|
||||
LOG("downloadRate [%f] reliable [%s] bestRepIdx [%d] noRepAvailable",
|
||||
downloadRate, (reliable ? "yes" : "no"), bestRepIdx,
|
||||
(noRepAvailable ? "yes" : "no"));
|
||||
|
||||
// If there is a higher bitrate stream that can be downloaded with the
|
||||
// current estimated bandwidth, step up to the next stream, for a graceful
|
||||
// increase in quality.
|
||||
uint32_t toDecoderIdx = mVideoRepDecoderIdx;
|
||||
if (bestRepIdx > toDecoderIdx) {
|
||||
toDecoderIdx = NS_MIN(toDecoderIdx+1, mVideoRepDecoders.Length()-1);
|
||||
} else if (toDecoderIdx < bestRepIdx) {
|
||||
// If the bitrate is too much for the current bandwidth, just use that
|
||||
// stream directly.
|
||||
toDecoderIdx = bestRepIdx;
|
||||
}
|
||||
NS_ENSURE_TRUE(toDecoderIdx < mVideoRepDecoders.Length(),
|
||||
NS_ERROR_ILLEGAL_VALUE);
|
||||
|
||||
// Notify reader and sub decoders and do the switch.
|
||||
if (toDecoderIdx != mVideoRepDecoderIdx) {
|
||||
LOG("*** Switching video decoder from [%d] [%p] to [%d] [%p] at "
|
||||
"subsegment [%d]", mVideoRepDecoderIdx, VideoRepDecoder(),
|
||||
toDecoderIdx, mVideoRepDecoders[toDecoderIdx].get(),
|
||||
mVideoSubsegmentIdx);
|
||||
|
||||
// Tell main reader to switch subreaders at |subsegmentIdx| - equates to
|
||||
// switching data source for reading.
|
||||
mDASHReader->RequestVideoReaderSwitch(mVideoRepDecoderIdx, toDecoderIdx,
|
||||
mVideoSubsegmentIdx);
|
||||
// Notify decoder it is about to be switched.
|
||||
mVideoRepDecoders[mVideoRepDecoderIdx]->PrepareForSwitch();
|
||||
// Switch video decoders - equates to switching download source.
|
||||
mVideoRepDecoderIdx = toDecoderIdx;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool
|
||||
DASHDecoder::IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder)
|
||||
{
|
||||
NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
// Only return true if |aRepDecoder| is active and metadata for all
|
||||
// representations has been downloaded.
|
||||
return ((aRepDecoder == AudioRepDecoder() && mAudioMetadataReadCount == 0) ||
|
||||
(aRepDecoder == VideoRepDecoder() && mVideoMetadataReadCount == 0));
|
||||
}
|
||||
|
||||
bool
|
||||
DASHDecoder::IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
|
||||
int32_t const aSubsegmentIdx)
|
||||
{
|
||||
NS_ASSERTION(aRepDecoder, "DASHRepDecoder pointer is null.");
|
||||
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
|
||||
// Return false if there is still metadata to be downloaded.
|
||||
if (mAudioMetadataReadCount != 0 || mVideoMetadataReadCount != 0) {
|
||||
return false;
|
||||
}
|
||||
// No audio switching; allow the audio decoder to download all subsegments.
|
||||
if (aRepDecoder == AudioRepDecoder()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int32_t videoDecoderIdx = GetRepIdxForVideoSubsegmentLoad(aSubsegmentIdx);
|
||||
if (aRepDecoder == mVideoRepDecoders[videoDecoderIdx]) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -66,7 +66,95 @@ public:
|
||||
// Called on the main thread only.
|
||||
void NotifyDownloadEnded(DASHRepDecoder* aRepDecoder,
|
||||
nsresult aStatus,
|
||||
MediaByteRange &aRange);
|
||||
int32_t const aSubsegmentIdx);
|
||||
|
||||
// Notification from an |MediaDecoderReader| class that metadata has been
|
||||
// read. Declared here to allow overloading.
|
||||
void OnReadMetadataCompleted() MOZ_OVERRIDE { }
|
||||
|
||||
// Notification from |DASHRepDecoder| that a metadata has been read.
|
||||
// |DASHDecoder| will initiate load of data bytes for active audio/video
|
||||
// decoders. Called on the decode thread.
|
||||
void OnReadMetadataCompleted(DASHRepDecoder* aRepDecoder);
|
||||
|
||||
// Refers to downloading data bytes, i.e. non metadata.
|
||||
// Returns true if |aRepDecoder| is an active audio or video sub decoder AND
|
||||
// if metadata for all audio or video decoders has been read.
|
||||
// Could be called from any thread; enters decoder monitor.
|
||||
bool IsDecoderAllowedToDownloadData(DASHRepDecoder* aRepDecoder);
|
||||
|
||||
// Refers to downloading data bytes during SEEKING.
|
||||
// Returns true if |aRepDecoder| is the active audio sub decoder, OR if
|
||||
// it is a video decoder and is allowed to download this subsegment.
|
||||
// Returns false if there is still some metadata to download.
|
||||
// Could be called from any thread; enters decoder monitor.
|
||||
bool IsDecoderAllowedToDownloadSubsegment(DASHRepDecoder* aRepDecoder,
|
||||
int32_t const aSubsegmentIdx);
|
||||
|
||||
// Determines if rep/sub decoders should be switched, and if so switches
|
||||
// them. Notifies |DASHReader| if and when it should switch readers.
|
||||
// Returns a pointer to the new active decoder.
|
||||
// Called on the main thread.
|
||||
nsresult PossiblySwitchDecoder(DASHRepDecoder* aRepDecoder);
|
||||
|
||||
// Sets the byte range index for audio|video downloads. Will only increment
|
||||
// for current active decoders. Could be called from any thread.
|
||||
// Requires monitor because of write to |mAudioSubsegmentIdx| or
|
||||
// |mVideoSubsegmentIdx|.
|
||||
void SetSubsegmentIndex(DASHRepDecoder* aRepDecoder,
|
||||
uint32_t aSubsegmentIdx)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (aRepDecoder == AudioRepDecoder()) {
|
||||
mAudioSubsegmentIdx = aSubsegmentIdx;
|
||||
} else if (aRepDecoder == VideoRepDecoder()) {
|
||||
mVideoSubsegmentIdx = aSubsegmentIdx;
|
||||
}
|
||||
}
|
||||
private:
|
||||
// Increments the byte range index for audio|video downloads. Will only
|
||||
// increment for current active decoders. Could be called from any thread.
|
||||
// Requires monitor because of write to |mAudioSubsegmentIdx| or
|
||||
// |mVideoSubsegmentIdx|.
|
||||
void IncrementSubsegmentIndex(DASHRepDecoder* aRepDecoder)
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
if (aRepDecoder == AudioRepDecoder()) {
|
||||
mAudioSubsegmentIdx++;
|
||||
} else if (aRepDecoder == VideoRepDecoder()) {
|
||||
mVideoSubsegmentIdx++;
|
||||
}
|
||||
}
|
||||
public:
|
||||
// Gets the byte range index for audio|video downloads. Will only increment
|
||||
// for current active decoders. Could be called from any thread. Will enter
|
||||
// monitor for read access off the decode thread.
|
||||
int32_t GetSubsegmentIndex(DASHRepDecoder* aRepDecoder)
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
|
||||
GetReentrantMonitor());
|
||||
if (aRepDecoder == AudioRepDecoder()) {
|
||||
return mAudioSubsegmentIdx;
|
||||
} else if (aRepDecoder == VideoRepDecoder()) {
|
||||
return mVideoSubsegmentIdx;
|
||||
}
|
||||
return (-1);
|
||||
}
|
||||
|
||||
// Returns the index of the rep decoder used to load a subsegment. Will enter
|
||||
// monitor for read access off the decode thread.
|
||||
int32_t GetRepIdxForVideoSubsegmentLoad(int32_t aSubsegmentIdx)
|
||||
{
|
||||
NS_ASSERTION(0 < aSubsegmentIdx, "Subsegment index should not be negative.");
|
||||
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
|
||||
GetReentrantMonitor());
|
||||
if ((uint32_t)aSubsegmentIdx < mVideoSubsegmentLoads.Length()) {
|
||||
return mVideoSubsegmentLoads[aSubsegmentIdx];
|
||||
} else {
|
||||
// If it hasn't been downloaded yet, use the lowest bitrate decoder.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Drop reference to state machine and tell sub-decoders to do the same.
|
||||
// Only called during shutdown dance, on main thread only.
|
||||
@ -107,6 +195,38 @@ private:
|
||||
nsresult CreateAudioRepDecoder(nsIURI* aUrl, Representation const * aRep);
|
||||
nsresult CreateVideoRepDecoder(nsIURI* aUrl, Representation const * aRep);
|
||||
|
||||
// Get audio sub-decoder for current audio |Representation|. Will return
|
||||
// nullptr for out of range indexes.
|
||||
// Enters monitor for read access off the decode thread.
|
||||
// XXX Note: Although an array of audio decoders is provided, audio stream
|
||||
// switching is not yet supported.
|
||||
DASHRepDecoder* AudioRepDecoder() {
|
||||
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
|
||||
GetReentrantMonitor());
|
||||
NS_ENSURE_TRUE((uint32_t)mAudioRepDecoderIdx < mAudioRepDecoders.Length(),
|
||||
nullptr);
|
||||
if (mAudioRepDecoderIdx < 0) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return mAudioRepDecoders[mAudioRepDecoderIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// Get video sub-decoder for current video |Representation|. Will return
|
||||
// nullptr for out of range indexes.
|
||||
// Enters monitor for read access off the decode thread.
|
||||
DASHRepDecoder* VideoRepDecoder() {
|
||||
ReentrantMonitorConditionallyEnter mon(!OnDecodeThread(),
|
||||
GetReentrantMonitor());
|
||||
NS_ENSURE_TRUE((uint32_t)mVideoRepDecoderIdx < mVideoRepDecoders.Length(),
|
||||
nullptr);
|
||||
if (mVideoRepDecoderIdx < 0) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return mVideoRepDecoders[mVideoRepDecoderIdx];
|
||||
}
|
||||
}
|
||||
|
||||
// Creates audio/video resources for individual |Representation|s.
|
||||
// On the main thread.
|
||||
MediaResource* CreateAudioSubResource(nsIURI* aUrl,
|
||||
@ -141,15 +261,37 @@ private:
|
||||
// state machine; destroyed in state machine's destructor.
|
||||
DASHReader* mDASHReader;
|
||||
|
||||
// Sub-decoder for current audio |Representation|.
|
||||
nsRefPtr<DASHRepDecoder> mAudioRepDecoder;
|
||||
// Array of pointers for the |Representation|s in the audio |AdaptationSet|.
|
||||
nsTArray<nsRefPtr<DASHRepDecoder> > mAudioRepDecoders;
|
||||
// Sub-decoder vars. Note: For all following members, the decode monitor
|
||||
// should be held for write access on decode thread, and all read/write off
|
||||
// the decode thread.
|
||||
|
||||
// Sub-decoder for current video |Representation|.
|
||||
nsRefPtr<DASHRepDecoder> mVideoRepDecoder;
|
||||
// Array of pointers for the |Representation|s in the video |AdaptationSet|.
|
||||
// Index of the video |AdaptationSet|.
|
||||
int32_t mVideoAdaptSetIdx;
|
||||
|
||||
// Indexes for the current audio and video decoders.
|
||||
int32_t mAudioRepDecoderIdx;
|
||||
int32_t mVideoRepDecoderIdx;
|
||||
|
||||
// Array of pointers for the |Representation|s in the audio/video
|
||||
// |AdaptationSet|.
|
||||
nsTArray<nsRefPtr<DASHRepDecoder> > mAudioRepDecoders;
|
||||
nsTArray<nsRefPtr<DASHRepDecoder> > mVideoRepDecoders;
|
||||
|
||||
// Current index of subsegments downloaded for audio/video decoder.
|
||||
int32_t mAudioSubsegmentIdx;
|
||||
int32_t mVideoSubsegmentIdx;
|
||||
|
||||
// Count for the number of readers which have called |OnReadMetadataCompleted|.
|
||||
// Initialised to 0; incremented for every decoder which has |Load| called;
|
||||
// and decremented for every call to |OnReadMetadataCompleted|. When it is
|
||||
// zero again, all metadata has been read for audio or video, and data bytes
|
||||
// can be downloaded.
|
||||
uint32_t mAudioMetadataReadCount;
|
||||
uint32_t mVideoMetadataReadCount;
|
||||
|
||||
// Array records the index of the decoder/Representation which loaded each
|
||||
// subsegment.
|
||||
nsTArray<int32_t> mVideoSubsegmentLoads;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -16,20 +16,46 @@
|
||||
#include "VideoFrameContainer.h"
|
||||
#include "AbstractMediaDecoder.h"
|
||||
#include "DASHReader.h"
|
||||
#include "DASHDecoder.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef PR_LOGGING
|
||||
extern PRLogModuleInfo* gMediaDecoderLog;
|
||||
#define LOG(msg, ...) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
|
||||
PRLogModuleInfo* gDASHReaderLog;
|
||||
#define LOG(msg, ...) PR_LOG(gDASHReaderLog, PR_LOG_DEBUG, \
|
||||
("%p [DASHReader] " msg, this, __VA_ARGS__))
|
||||
#define LOG1(msg) PR_LOG(gMediaDecoderLog, PR_LOG_DEBUG, \
|
||||
#define LOG1(msg) PR_LOG(gDASHReaderLog, PR_LOG_DEBUG, \
|
||||
("%p [DASHReader] " msg, this))
|
||||
#else
|
||||
#define LOG(msg, ...)
|
||||
#define LOG1(msg)
|
||||
#endif
|
||||
|
||||
DASHReader::DASHReader(AbstractMediaDecoder* aDecoder) :
|
||||
MediaDecoderReader(aDecoder),
|
||||
mReadMetadataMonitor("media.dashreader.readmetadata"),
|
||||
mReadyToReadMetadata(false),
|
||||
mDecoderIsShuttingDown(false),
|
||||
mAudioReader(this),
|
||||
mVideoReader(this),
|
||||
mAudioReaders(this),
|
||||
mVideoReaders(this),
|
||||
mSwitchVideoReaders(false),
|
||||
mSwitchCount(-1)
|
||||
{
|
||||
MOZ_COUNT_CTOR(DASHReader);
|
||||
#ifdef PR_LOGGING
|
||||
if (!gDASHReaderLog) {
|
||||
gDASHReaderLog = PR_NewLogModule("DASHReader");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
DASHReader::~DASHReader()
|
||||
{
|
||||
MOZ_COUNT_DTOR(DASHReader);
|
||||
}
|
||||
|
||||
nsresult
|
||||
DASHReader::Init(MediaDecoderReader* aCloneDonor)
|
||||
{
|
||||
@ -52,7 +78,7 @@ DASHReader::Init(MediaDecoderReader* aCloneDonor)
|
||||
}
|
||||
|
||||
void
|
||||
DASHReader::AddAudioReader(MediaDecoderReader* aAudioReader)
|
||||
DASHReader::AddAudioReader(DASHRepReader* aAudioReader)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(aAudioReader, );
|
||||
@ -66,7 +92,7 @@ DASHReader::AddAudioReader(MediaDecoderReader* aAudioReader)
|
||||
}
|
||||
|
||||
void
|
||||
DASHReader::AddVideoReader(MediaDecoderReader* aVideoReader)
|
||||
DASHReader::AddVideoReader(DASHRepReader* aVideoReader)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ENSURE_TRUE(aVideoReader, );
|
||||
@ -79,12 +105,26 @@ DASHReader::AddVideoReader(MediaDecoderReader* aVideoReader)
|
||||
mVideoReader = aVideoReader;
|
||||
}
|
||||
|
||||
bool
|
||||
DASHReader::HasAudio()
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return mAudioReader ? mAudioReader->HasAudio() : false;
|
||||
}
|
||||
|
||||
bool
|
||||
DASHReader::HasVideo()
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return mVideoReader ? mVideoReader->HasVideo() : false;
|
||||
}
|
||||
|
||||
int64_t
|
||||
DASHReader::VideoQueueMemoryInUse()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
return (mVideoReader ? mVideoReader->VideoQueueMemoryInUse() : 0);
|
||||
return VideoQueueMemoryInUse();
|
||||
}
|
||||
|
||||
int64_t
|
||||
@ -92,7 +132,7 @@ DASHReader::AudioQueueMemoryInUse()
|
||||
{
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
return (mAudioReader ? mAudioReader->AudioQueueMemoryInUse() : 0);
|
||||
return AudioQueueMemoryInUse();
|
||||
}
|
||||
|
||||
bool
|
||||
@ -116,7 +156,7 @@ DASHReader::DecodeAudioData()
|
||||
|
||||
nsresult
|
||||
DASHReader::ReadMetadata(VideoInfo* aInfo,
|
||||
MetadataTags** aTags)
|
||||
MetadataTags** aTags)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
@ -130,15 +170,27 @@ DASHReader::ReadMetadata(VideoInfo* aInfo,
|
||||
// Verify no other errors before continuing.
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
NS_ASSERTION(aTags, "Called with null MetadataTags**.");
|
||||
*aTags = nullptr;
|
||||
|
||||
// Get metadata from child readers.
|
||||
VideoInfo audioInfo, videoInfo;
|
||||
|
||||
if (mVideoReader) {
|
||||
rv = mVideoReader->ReadMetadata(&videoInfo, aTags);
|
||||
// Read metadata for all video streams.
|
||||
for (uint i = 0; i < mVideoReaders.Length(); i++) {
|
||||
// Use an nsAutoPtr here to ensure |tags| memory does not leak.
|
||||
nsAutoPtr<nsHTMLMediaElement::MetadataTags> tags;
|
||||
rv = mVideoReaders[i]->ReadMetadata(&videoInfo, getter_Transfers(tags));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mInfo.mHasVideo = videoInfo.mHasVideo;
|
||||
mInfo.mDisplay = videoInfo.mDisplay;
|
||||
// Use metadata from current video sub reader to populate aInfo.
|
||||
if (mVideoReaders[i] == mVideoReader) {
|
||||
mInfo.mHasVideo = videoInfo.mHasVideo;
|
||||
mInfo.mDisplay = videoInfo.mDisplay;
|
||||
}
|
||||
}
|
||||
// Read metadata for audio stream.
|
||||
// Note: Getting metadata tags from audio reader only for now.
|
||||
// XXX Audio stream switching not yet supported.
|
||||
if (mAudioReader) {
|
||||
rv = mAudioReader->ReadMetadata(&audioInfo, aTags);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
@ -303,7 +355,7 @@ DASHReader::AudioQueue()
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
NS_ASSERTION(mAudioReader, "mAudioReader is NULL!");
|
||||
return mAudioReader->AudioQueue();
|
||||
return mAudioQueue;
|
||||
}
|
||||
|
||||
MediaQueue<VideoData>&
|
||||
@ -312,7 +364,7 @@ DASHReader::VideoQueue()
|
||||
ReentrantMonitorConditionallyEnter mon(!mDecoder->OnDecodeThread(),
|
||||
mDecoder->GetReentrantMonitor());
|
||||
NS_ASSERTION(mVideoReader, "mVideoReader is NULL!");
|
||||
return mVideoReader->VideoQueue();
|
||||
return mVideoQueue;
|
||||
}
|
||||
|
||||
bool
|
||||
@ -326,5 +378,121 @@ DASHReader::IsSeekableInBufferedRanges()
|
||||
(mAudioReader && !mAudioReader->IsSeekableInBufferedRanges()));
|
||||
}
|
||||
|
||||
void
|
||||
DASHReader::RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
|
||||
uint32_t aToReaderIdx,
|
||||
uint32_t aSubsegmentIdx)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
NS_ASSERTION(aFromReaderIdx < mVideoReaders.Length(),
|
||||
"From index is greater than number of video readers!");
|
||||
NS_ASSERTION(aToReaderIdx < mVideoReaders.Length(),
|
||||
"To index is greater than number of video readers!");
|
||||
NS_ASSERTION(aToReaderIdx != aFromReaderIdx,
|
||||
"Don't request switches to same reader!");
|
||||
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
|
||||
if (mSwitchCount < 0) {
|
||||
mSwitchCount = 0;
|
||||
}
|
||||
|
||||
DASHRepReader* fromReader = mVideoReaders[aFromReaderIdx];
|
||||
DASHRepReader* toReader = mVideoReaders[aToReaderIdx];
|
||||
|
||||
LOG("Switch requested from reader [%d] [%p] to reader [%d] [%p] "
|
||||
"at subsegment[%d].",
|
||||
aFromReaderIdx, fromReader, aToReaderIdx, toReader, aSubsegmentIdx);
|
||||
|
||||
// Append the subsegment index to the list of pending switches.
|
||||
mSwitchToVideoSubsegmentIndexes.AppendElement(aSubsegmentIdx);
|
||||
|
||||
// Tell the SWITCH FROM reader when it should stop reading.
|
||||
fromReader->RequestSwitchAtSubsegment(aSubsegmentIdx, toReader);
|
||||
|
||||
// Tell the SWITCH TO reader to seek to the correct offset.
|
||||
toReader->RequestSeekToSubsegment(aSubsegmentIdx);
|
||||
|
||||
mSwitchVideoReaders = true;
|
||||
}
|
||||
|
||||
void
|
||||
DASHReader::PossiblySwitchVideoReaders()
|
||||
{
|
||||
NS_ASSERTION(mDecoder, "Decoder should not be null");
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
// Flag to switch streams is set in |RequestVideoReaderSwitch|.
|
||||
if (!mSwitchVideoReaders) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only switch if we reached a switch access point.
|
||||
NS_ENSURE_TRUE(0 <= mSwitchCount, );
|
||||
NS_ENSURE_TRUE((uint32_t)mSwitchCount < mSwitchToVideoSubsegmentIndexes.Length(), );
|
||||
uint32_t switchIdx = mSwitchToVideoSubsegmentIndexes[mSwitchCount];
|
||||
if (!mVideoReader->HasReachedSubsegment(switchIdx)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get Representation index to switch to.
|
||||
DASHDecoder* dashDecoder = static_cast<DASHDecoder*>(mDecoder);
|
||||
int32_t toReaderIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(switchIdx);
|
||||
NS_ENSURE_TRUE(0 <= toReaderIdx, );
|
||||
NS_ENSURE_TRUE((uint32_t)toReaderIdx < mVideoReaders.Length(), );
|
||||
|
||||
DASHRepReader* fromReader = mVideoReader;
|
||||
DASHRepReader* toReader = mVideoReaders[toReaderIdx];
|
||||
NS_ENSURE_TRUE(fromReader != toReader, );
|
||||
|
||||
LOG("Switching video readers now from [%p] to [%p] at subsegment [%d]: "
|
||||
"mSwitchCount [%d].",
|
||||
fromReader, toReader, switchIdx, mSwitchCount);
|
||||
|
||||
// Switch readers while in the monitor.
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
mVideoReader = toReader;
|
||||
|
||||
// Prep readers for next switch, also while in monitor.
|
||||
if ((uint32_t)++mSwitchCount < mSwitchToVideoSubsegmentIndexes.Length()) {
|
||||
// Get the subsegment at which to switch.
|
||||
switchIdx = mSwitchToVideoSubsegmentIndexes[mSwitchCount];
|
||||
|
||||
// Update from and to reader ptrs for next switch.
|
||||
fromReader = toReader;
|
||||
toReaderIdx = dashDecoder->GetRepIdxForVideoSubsegmentLoad(switchIdx);
|
||||
toReader = mVideoReaders[toReaderIdx];
|
||||
NS_ENSURE_TRUE((uint32_t)toReaderIdx < mVideoReaders.Length(), );
|
||||
NS_ENSURE_TRUE(fromReader != toReader, );
|
||||
|
||||
// Tell the SWITCH FROM reader when it should stop reading.
|
||||
fromReader->RequestSwitchAtSubsegment(switchIdx, toReader);
|
||||
|
||||
// Tell the SWITCH TO reader to seek to the correct offset.
|
||||
toReader->RequestSeekToSubsegment(switchIdx);
|
||||
} else {
|
||||
// If there are no more pending switches, unset the switch readers flag.
|
||||
mSwitchVideoReaders = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DASHReader::PrepareToDecode()
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
// Flag to switch streams is set by |DASHDecoder|.
|
||||
if (!mSwitchVideoReaders) {
|
||||
return;
|
||||
}
|
||||
|
||||
PossiblySwitchVideoReaders();
|
||||
|
||||
// Prepare each sub reader for decoding: includes seeking to the correct
|
||||
// offset if a seek was previously requested.
|
||||
for (uint32_t i = 0; i < mVideoReaders.Length(); i++) {
|
||||
mVideoReaders[i]->PrepareToDecode();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -16,34 +16,24 @@
|
||||
#if !defined(DASHReader_h_)
|
||||
#define DASHReader_h_
|
||||
|
||||
#include "VideoUtils.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "DASHRepReader.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class DASHRepReader;
|
||||
|
||||
class DASHReader : public MediaDecoderReader
|
||||
{
|
||||
public:
|
||||
DASHReader(AbstractMediaDecoder* aDecoder) :
|
||||
MediaDecoderReader(aDecoder),
|
||||
mReadMetadataMonitor("media.dashreader.readmetadata"),
|
||||
mReadyToReadMetadata(false),
|
||||
mDecoderIsShuttingDown(false),
|
||||
mAudioReader(this),
|
||||
mVideoReader(this),
|
||||
mAudioReaders(this),
|
||||
mVideoReaders(this)
|
||||
{
|
||||
MOZ_COUNT_CTOR(DASHReader);
|
||||
}
|
||||
~DASHReader()
|
||||
{
|
||||
MOZ_COUNT_DTOR(DASHReader);
|
||||
}
|
||||
DASHReader(AbstractMediaDecoder* aDecoder);
|
||||
~DASHReader();
|
||||
|
||||
// Adds a pointer to a audio/video reader for a media |Representation|.
|
||||
// Called on the main thread only.
|
||||
void AddAudioReader(MediaDecoderReader* aAudioReader);
|
||||
void AddVideoReader(MediaDecoderReader* aVideoReader);
|
||||
void AddAudioReader(DASHRepReader* aAudioReader);
|
||||
void AddVideoReader(DASHRepReader* aVideoReader);
|
||||
|
||||
// Waits for metadata bytes to be downloaded, then reads and parses them.
|
||||
// Called on the decode thread only.
|
||||
@ -89,19 +79,13 @@ public:
|
||||
|
||||
// Audio/video status are dependent on the presence of audio/video readers.
|
||||
// Call on decode thread only.
|
||||
bool HasAudio() {
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return mAudioReader ? mAudioReader->HasAudio() : false;
|
||||
}
|
||||
bool HasVideo() {
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
return mVideoReader ? mVideoReader->HasVideo() : false;
|
||||
}
|
||||
bool HasAudio();
|
||||
bool HasVideo();
|
||||
|
||||
// Returns references to the audio/video queues of sub-readers. Called on
|
||||
// decode, state machine and audio threads.
|
||||
MediaQueue<AudioData>& AudioQueue();
|
||||
MediaQueue<VideoData>& VideoQueue();
|
||||
MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE;
|
||||
MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE;
|
||||
|
||||
// Called from MediaDecoderStateMachine on the main thread.
|
||||
nsresult Init(MediaDecoderReader* aCloneDonor);
|
||||
@ -110,6 +94,11 @@ public:
|
||||
int64_t VideoQueueMemoryInUse();
|
||||
int64_t AudioQueueMemoryInUse();
|
||||
|
||||
// Called on the decode thread, at the start of the decode loop, before
|
||||
// |DecodeVideoFrame|. Carries out video reader switch if previously
|
||||
// requested, and tells sub-readers to |PrepareToDecode|.
|
||||
void PrepareToDecode() MOZ_OVERRIDE;
|
||||
|
||||
// Called on the decode thread.
|
||||
bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold);
|
||||
bool DecodeAudioData();
|
||||
@ -129,45 +118,20 @@ public:
|
||||
// Call by state machine on multiple threads.
|
||||
bool IsSeekableInBufferedRanges();
|
||||
|
||||
private:
|
||||
// Similar to |ReentrantMonitorAutoEnter|, this class enters the supplied
|
||||
// monitor in its constructor, but only if the conditional value |aEnter| is
|
||||
// true. Used here to allow read access on the sub-readers' owning thread,
|
||||
// i.e. the decode thread, while locking write accesses from all threads,
|
||||
// and read accesses from non-decode threads.
|
||||
class ReentrantMonitorConditionallyEnter
|
||||
{
|
||||
public:
|
||||
ReentrantMonitorConditionallyEnter(bool aEnter,
|
||||
ReentrantMonitor &aReentrantMonitor) :
|
||||
mReentrantMonitor(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(DASHReader::ReentrantMonitorConditionallyEnter);
|
||||
if (aEnter) {
|
||||
mReentrantMonitor = &aReentrantMonitor;
|
||||
NS_ASSERTION(mReentrantMonitor, "null monitor");
|
||||
mReentrantMonitor->Enter();
|
||||
}
|
||||
}
|
||||
~ReentrantMonitorConditionallyEnter(void)
|
||||
{
|
||||
if (mReentrantMonitor) {
|
||||
mReentrantMonitor->Exit();
|
||||
}
|
||||
MOZ_COUNT_DTOR(DASHReader::ReentrantMonitorConditionallyEnter);
|
||||
}
|
||||
private:
|
||||
// Restrict to constructor and destructor defined above.
|
||||
ReentrantMonitorConditionallyEnter();
|
||||
ReentrantMonitorConditionallyEnter(const ReentrantMonitorConditionallyEnter&);
|
||||
ReentrantMonitorConditionallyEnter& operator =(const ReentrantMonitorConditionallyEnter&);
|
||||
static void* operator new(size_t) CPP_THROW_NEW;
|
||||
static void operator delete(void*);
|
||||
// Prepares for an upcoming switch of video readers. Called by
|
||||
// |DASHDecoder| when it has switched download streams. Sets the index of
|
||||
// the reader to switch TO and the index of the subsegment to switch AT
|
||||
// (start offset). (Note: Subsegment boundaries are switch access points for
|
||||
// DASH-WebM). Called on the main thread. Must be in the decode monitor.
|
||||
void RequestVideoReaderSwitch(uint32_t aFromReaderIdx,
|
||||
uint32_t aToReaderIdx,
|
||||
uint32_t aSubsegmentIdx);
|
||||
|
||||
// Ptr to the |ReentrantMonitor| object. Null if |aEnter| in constructor
|
||||
// was false.
|
||||
ReentrantMonitor* mReentrantMonitor;
|
||||
};
|
||||
private:
|
||||
// Switches video subreaders if a stream-switch flag has been set, and the
|
||||
// current reader has read up to the switching subsegment (start offset).
|
||||
// Called on the decode thread only.
|
||||
void PossiblySwitchVideoReaders();
|
||||
|
||||
// Monitor and booleans used to wait for metadata bytes to be downloaded, and
|
||||
// skip reading metadata if |DASHDecoder|'s shutdown is in progress.
|
||||
@ -199,7 +163,7 @@ private:
|
||||
|
||||
// Override '=' to always assert thread is "in monitor" for writes/changes
|
||||
// to |mSubReader|.
|
||||
MonitoredSubReader& operator=(MediaDecoderReader* rhs)
|
||||
MonitoredSubReader& operator=(DASHRepReader* rhs)
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
@ -209,7 +173,7 @@ private:
|
||||
|
||||
// Override '*' to assert threads other than the decode thread are "in
|
||||
// monitor" for ptr reads.
|
||||
operator MediaDecoderReader*() const
|
||||
operator DASHRepReader*() const
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
if (!mReader->GetDecoder()->OnDecodeThread()) {
|
||||
@ -220,7 +184,7 @@ private:
|
||||
|
||||
// Override '->' to assert threads other than the decode thread are "in
|
||||
// monitor" for |mSubReader| function calls.
|
||||
MediaDecoderReader* operator->() const
|
||||
DASHRepReader* operator->() const
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
@ -228,7 +192,7 @@ private:
|
||||
// Pointer to |DASHReader| object which owns this |MonitoredSubReader|.
|
||||
DASHReader* mReader;
|
||||
// Ref ptr to the sub reader.
|
||||
nsRefPtr<MediaDecoderReader> mSubReader;
|
||||
nsRefPtr<DASHRepReader> mSubReader;
|
||||
};
|
||||
|
||||
// Wrapped ref ptrs to current sub-readers of individual media
|
||||
@ -277,7 +241,7 @@ private:
|
||||
// Override '[]' to assert threads other than the decode thread are "in
|
||||
// monitor" for accessing individual elems. Note: elems returned do not
|
||||
// have monitor assertions builtin like |MonitoredSubReader| objects.
|
||||
nsRefPtr<MediaDecoderReader>& operator[](uint32_t i)
|
||||
nsRefPtr<DASHRepReader>& operator[](uint32_t i)
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
if (!mReader->GetDecoder()->OnDecodeThread()) {
|
||||
@ -289,7 +253,7 @@ private:
|
||||
// Appends a reader to the end of |mSubReaderList|. Will always assert that
|
||||
// the thread is "in monitor".
|
||||
void
|
||||
AppendElement(MediaDecoderReader* aReader)
|
||||
AppendElement(DASHRepReader* aReader)
|
||||
{
|
||||
NS_ASSERTION(mReader->GetDecoder(), "Decoder is null!");
|
||||
mReader->GetDecoder()->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
@ -299,7 +263,7 @@ private:
|
||||
// Pointer to |DASHReader| object which owns this |MonitoredSubReader|.
|
||||
DASHReader* mReader;
|
||||
// Ref ptrs to the sub readers.
|
||||
nsTArray<nsRefPtr<MediaDecoderReader> > mSubReaderList;
|
||||
nsTArray<nsRefPtr<DASHRepReader> > mSubReaderList;
|
||||
};
|
||||
|
||||
// Ref ptrs to all sub-readers of individual media |Representation|s.
|
||||
@ -308,6 +272,18 @@ private:
|
||||
// decode thread does not need to be protected.
|
||||
MonitoredSubReaderList mAudioReaders;
|
||||
MonitoredSubReaderList mVideoReaders;
|
||||
|
||||
// When true, indicates that we should switch reader. Must be in the monitor
|
||||
// for write access and read access off the decode thread.
|
||||
bool mSwitchVideoReaders;
|
||||
|
||||
// Indicates the subsegment index at which the reader should switch. Must be
|
||||
// in the monitor for write access and read access off the decode thread.
|
||||
nsTArray<uint32_t> mSwitchToVideoSubsegmentIndexes;
|
||||
|
||||
// Counts the number of switches that have taken place. Must be in the
|
||||
// monitor for write access and read access off the decode thread.
|
||||
int32_t mSwitchCount;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "DASHReader.h"
|
||||
#include "MediaResource.h"
|
||||
#include "DASHRepDecoder.h"
|
||||
#include "WebMReader.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@ -133,16 +134,24 @@ DASHRepDecoder::NotifyDownloadEnded(nsresult aStatus)
|
||||
// Decrement counter as metadata chunks are downloaded.
|
||||
// Note: Reader gets next chunk download via |ChannelMediaResource|:|Seek|.
|
||||
if (mMetadataChunkCount > 0) {
|
||||
LOG("Metadata chunk [%d] downloaded: range requested [%d - %d]",
|
||||
LOG("Metadata chunk [%d] downloaded: range requested [%lld - %lld] "
|
||||
"subsegmentIdx [%d]",
|
||||
mMetadataChunkCount,
|
||||
mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
|
||||
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx);
|
||||
mMetadataChunkCount--;
|
||||
} else {
|
||||
LOG("Byte range downloaded: status [%x] range requested [%lld - %lld] "
|
||||
"subsegmentIdx [%d]",
|
||||
aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd,
|
||||
mSubsegmentIdx);
|
||||
if ((uint32_t)mSubsegmentIdx == mByteRanges.Length()-1) {
|
||||
mResource->NotifyLastByteRange();
|
||||
}
|
||||
// Notify main decoder that a DATA byte range is downloaded.
|
||||
LOG("Byte range downloaded: status [%x] range requested [%d - %d]",
|
||||
aStatus, mCurrentByteRange.mStart, mCurrentByteRange.mEnd);
|
||||
mMainDecoder->NotifyDownloadEnded(this, aStatus,
|
||||
mCurrentByteRange);
|
||||
// Only notify IF this decoder is allowed to download data.
|
||||
NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
|
||||
"This decoder should not have downloaded data.");
|
||||
mMainDecoder->NotifyDownloadEnded(this, aStatus, mSubsegmentIdx);
|
||||
}
|
||||
} else if (aStatus == NS_BINDING_ABORTED) {
|
||||
LOG("MPD download has been cancelled by the user: aStatus [%x].", aStatus);
|
||||
@ -161,63 +170,94 @@ DASHRepDecoder::OnReadMetadataCompleted()
|
||||
{
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
// If shutting down, just return silently.
|
||||
if (mShuttingDown) {
|
||||
LOG1("Shutting down! Ignoring OnReadMetadataCompleted().");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG1("Metadata has been read.");
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NewRunnableMethod(this, &DASHRepDecoder::LoadNextByteRange);
|
||||
nsresult rv = NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error dispatching parse event to main thread: rv[%x]", rv);
|
||||
|
||||
// Metadata loaded and read for this stream; ok to populate byte ranges.
|
||||
nsresult rv = PopulateByteRanges();
|
||||
if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
|
||||
LOG("Error populating byte ranges [%x]", rv);
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
mMainDecoder->OnReadMetadataCompleted(this);
|
||||
}
|
||||
|
||||
nsresult
|
||||
DASHRepDecoder::PopulateByteRanges()
|
||||
{
|
||||
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
// Should not be called during shutdown.
|
||||
NS_ENSURE_FALSE(mShuttingDown, NS_ERROR_UNEXPECTED);
|
||||
|
||||
if (!mByteRanges.IsEmpty()) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_ENSURE_TRUE(mReader, NS_ERROR_NULL_POINTER);
|
||||
LOG1("Populating byte range array.");
|
||||
return mReader->GetSubsegmentByteRanges(mByteRanges);
|
||||
}
|
||||
|
||||
void
|
||||
DASHRepDecoder::LoadNextByteRange()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
if (!mResource) {
|
||||
LOG1("Error: resource is reported as null!");
|
||||
NS_ASSERTION(mResource, "Error: resource is reported as null!");
|
||||
|
||||
// Return silently if shutting down.
|
||||
if (mShuttingDown) {
|
||||
LOG1("Shutting down! Ignoring LoadNextByteRange().");
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ASSERTION(mMainDecoder, "Error: main decoder is null!");
|
||||
NS_ASSERTION(mMainDecoder->IsDecoderAllowedToDownloadData(this),
|
||||
"Should not be called on non-active decoders!");
|
||||
|
||||
// Cannot have empty byte ranges.
|
||||
if (mByteRanges.IsEmpty()) {
|
||||
LOG1("Error getting list of subsegment byte ranges.");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate the array of subsegment byte ranges if it's empty.
|
||||
nsresult rv;
|
||||
if (mByteRanges.IsEmpty()) {
|
||||
if (!mReader) {
|
||||
LOG1("Error: mReader should not be null!");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
rv = mReader->GetIndexByteRanges(mByteRanges);
|
||||
// If empty, just fail.
|
||||
if (NS_FAILED(rv) || mByteRanges.IsEmpty()) {
|
||||
LOG1("Error getting list of subsegment byte ranges.");
|
||||
DecodeError();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get byte range for subsegment.
|
||||
if (mSubsegmentIdx < mByteRanges.Length()) {
|
||||
mCurrentByteRange = mByteRanges[mSubsegmentIdx];
|
||||
int32_t subsegmentIdx = mMainDecoder->GetSubsegmentIndex(this);
|
||||
NS_ASSERTION(0 <= subsegmentIdx,
|
||||
"Subsegment index should be >= 0 for active decoders");
|
||||
if (subsegmentIdx >= 0 && (uint32_t)subsegmentIdx < mByteRanges.Length()) {
|
||||
mCurrentByteRange = mByteRanges[subsegmentIdx];
|
||||
mSubsegmentIdx = subsegmentIdx;
|
||||
} else {
|
||||
mCurrentByteRange.Clear();
|
||||
LOG("End of subsegments: index [%d] out of range.", mSubsegmentIdx);
|
||||
mSubsegmentIdx = -1;
|
||||
LOG("End of subsegments: index [%d] out of range.", subsegmentIdx);
|
||||
return;
|
||||
}
|
||||
|
||||
// Request a seek for the first reader. Required so that the reader is
|
||||
// primed to start here, and will block subsequent subsegment seeks unless
|
||||
// the subsegment has been read.
|
||||
if (subsegmentIdx == 0) {
|
||||
ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
|
||||
mReader->RequestSeekToSubsegment(0);
|
||||
}
|
||||
|
||||
// Open byte range corresponding to subsegment.
|
||||
rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
|
||||
nsresult rv = mResource->OpenByteRange(nullptr, mCurrentByteRange);
|
||||
if (NS_FAILED(rv)) {
|
||||
LOG("Error opening byte range [%d - %d]: rv [%x].",
|
||||
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, rv);
|
||||
LOG("Error opening byte range [%lld - %lld]: subsegmentIdx [%d] rv [%x].",
|
||||
mCurrentByteRange.mStart, mCurrentByteRange.mEnd, mSubsegmentIdx, rv);
|
||||
NetworkError();
|
||||
return;
|
||||
}
|
||||
// Increment subsegment index for next load.
|
||||
mSubsegmentIdx++;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -226,41 +266,78 @@ DASHRepDecoder::GetByteRangeForSeek(int64_t const aOffset,
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
// Check data ranges, if available.
|
||||
for (int i = 0; i < mByteRanges.Length(); i++) {
|
||||
NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
|
||||
if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
|
||||
mCurrentByteRange = aByteRange = mByteRanges[i];
|
||||
mSubsegmentIdx = i;
|
||||
// Only check data ranges if they're available and if this decoder is active,
|
||||
// i.e. inactive rep decoders should only load metadata.
|
||||
bool canDownloadData = mMainDecoder->IsDecoderAllowedToDownloadData(this);
|
||||
if (canDownloadData) {
|
||||
for (int i = 0; i < mByteRanges.Length(); i++) {
|
||||
NS_ENSURE_FALSE(mByteRanges[i].IsNull(), NS_ERROR_NOT_INITIALIZED);
|
||||
// Check if |aOffset| lies within the current data range.
|
||||
if (mByteRanges[i].mStart <= aOffset && aOffset <= mByteRanges[i].mEnd) {
|
||||
mCurrentByteRange = aByteRange = mByteRanges[i];
|
||||
mSubsegmentIdx = i;
|
||||
// XXX Hack: should be setting subsegment outside this function, but
|
||||
// need to review seeking for multiple switches anyhow.
|
||||
mMainDecoder->SetSubsegmentIndex(this, i);
|
||||
LOG("Getting DATA range [%d] for seek offset [%lld]: "
|
||||
"bytes [%lld] to [%lld]",
|
||||
i, aOffset, aByteRange.mStart, aByteRange.mEnd);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG1("Restricting seekable byte ranges to metadata for this decoder.");
|
||||
}
|
||||
// Don't allow metadata downloads once they're loaded and byte ranges have
|
||||
// been populated.
|
||||
bool canDownloadMetadata = mByteRanges.IsEmpty();
|
||||
if (canDownloadMetadata) {
|
||||
// Check metadata ranges; init range.
|
||||
if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
|
||||
mCurrentByteRange = aByteRange = mInitByteRange;
|
||||
mSubsegmentIdx = 0;
|
||||
LOG("Getting INIT range for seek offset [%lld]: bytes [%lld] to "
|
||||
"[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
// Check metadata ranges; init range.
|
||||
if (mInitByteRange.mStart <= aOffset && aOffset <= mInitByteRange.mEnd) {
|
||||
mCurrentByteRange = aByteRange = mInitByteRange;
|
||||
mSubsegmentIdx = 0;
|
||||
return NS_OK;
|
||||
}
|
||||
// ... index range.
|
||||
if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
|
||||
mCurrentByteRange = aByteRange = mIndexByteRange;
|
||||
mSubsegmentIdx = 0;
|
||||
return NS_OK;
|
||||
// ... index range.
|
||||
if (mIndexByteRange.mStart <= aOffset && aOffset <= mIndexByteRange.mEnd) {
|
||||
mCurrentByteRange = aByteRange = mIndexByteRange;
|
||||
mSubsegmentIdx = 0;
|
||||
LOG("Getting INDEXES range for seek offset [%lld]: bytes [%lld] to "
|
||||
"[%lld]", aOffset, aByteRange.mStart, aByteRange.mEnd);
|
||||
return NS_OK;
|
||||
}
|
||||
} else {
|
||||
LOG1("Metadata should be read; inhibiting further metadata downloads.");
|
||||
}
|
||||
|
||||
// If no byte range is found by this stage, clear the parameter and return.
|
||||
aByteRange.Clear();
|
||||
if (mByteRanges.IsEmpty()) {
|
||||
if (mByteRanges.IsEmpty() || !canDownloadData || !canDownloadMetadata) {
|
||||
// Assume mByteRanges will be populated after metadata is read.
|
||||
LOG("Can't get range for offset [%d].", aOffset);
|
||||
LOG("Data ranges not populated [%s]; data download restricted [%s]; "
|
||||
"metadata download restricted [%s]: offset[%lld].",
|
||||
(mByteRanges.IsEmpty() ? "yes" : "no"),
|
||||
(canDownloadData ? "no" : "yes"),
|
||||
(canDownloadMetadata ? "no" : "yes"), aOffset);
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
} else {
|
||||
// Cannot seek to an unknown offset.
|
||||
// XXX Revisit this for dynamic MPD profiles if MPD is regularly updated.
|
||||
LOG("Error! Offset [%d] is in an unknown range!", aOffset);
|
||||
LOG("Error! Offset [%lld] is in an unknown range!", aOffset);
|
||||
return NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DASHRepDecoder::PrepareForSwitch()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
// Ensure that the media cache writes any data held in its partial block.
|
||||
mResource->FlushCache();
|
||||
}
|
||||
|
||||
void
|
||||
DASHRepDecoder::NetworkError()
|
||||
{
|
||||
@ -302,7 +379,7 @@ DASHRepDecoder::NotifyDataArrived(const char* aBuffer,
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
|
||||
|
||||
LOG("Data bytes [%d - %d] arrived via buffer [%p].",
|
||||
LOG("Data bytes [%lld - %lld] arrived via buffer [%p].",
|
||||
aOffset, aOffset+aLength, aBuffer);
|
||||
// Notify reader directly, since call to |MediaDecoderStateMachine|::
|
||||
// |NotifyDataArrived| will go to |DASHReader|::|NotifyDataArrived|, which
|
||||
|
@ -25,6 +25,7 @@
|
||||
namespace mozilla {
|
||||
|
||||
class DASHDecoder;
|
||||
class DASHRepReader;
|
||||
|
||||
class DASHRepDecoder : public MediaDecoder
|
||||
{
|
||||
@ -39,7 +40,7 @@ public:
|
||||
mMPDRepresentation(nullptr),
|
||||
mMetadataChunkCount(0),
|
||||
mCurrentByteRange(),
|
||||
mSubsegmentIdx(0),
|
||||
mSubsegmentIdx(-1),
|
||||
mReader(nullptr)
|
||||
{
|
||||
MOZ_COUNT_CTOR(DASHRepDecoder);
|
||||
@ -128,6 +129,14 @@ public:
|
||||
nsresult GetByteRangeForSeek(int64_t const aOffset,
|
||||
MediaByteRange& aByteRange);
|
||||
|
||||
// Gets the number of data byte ranges (not inc. metadata).
|
||||
uint32_t GetNumDataByteRanges() {
|
||||
return mByteRanges.Length();
|
||||
}
|
||||
|
||||
// Notify that a switch is about to happen. Called on the main thread.
|
||||
void PrepareForSwitch();
|
||||
|
||||
// Returns true if the current thread is the state machine thread.
|
||||
bool OnStateMachineThread() const;
|
||||
|
||||
@ -135,14 +144,14 @@ public:
|
||||
bool OnDecodeThread() const;
|
||||
|
||||
// Returns main decoder's monitor for synchronised access.
|
||||
ReentrantMonitor& GetReentrantMonitor();
|
||||
ReentrantMonitor& GetReentrantMonitor() MOZ_OVERRIDE;
|
||||
|
||||
// Called on the decode thread from WebMReader.
|
||||
ImageContainer* GetImageContainer();
|
||||
|
||||
// Called when Metadata has been read; notifies that index data is read.
|
||||
// Called on the decode thread only.
|
||||
void OnReadMetadataCompleted();
|
||||
void OnReadMetadataCompleted() MOZ_OVERRIDE;
|
||||
|
||||
// Overridden to cleanup ref to |DASHDecoder|. Called on main thread only.
|
||||
void Shutdown() {
|
||||
@ -162,6 +171,10 @@ public:
|
||||
void DecodeError();
|
||||
|
||||
private:
|
||||
// Populates |mByteRanges| by calling |GetIndexByteRanges| from |mReader|.
|
||||
// Called on the main thread only.
|
||||
nsresult PopulateByteRanges();
|
||||
|
||||
// The main decoder.
|
||||
nsRefPtr<DASHDecoder> mMainDecoder;
|
||||
// This decoder's MPD |Representation| object.
|
||||
@ -179,12 +192,12 @@ private:
|
||||
|
||||
// The current byte range being requested.
|
||||
MediaByteRange mCurrentByteRange;
|
||||
// Index of the current byte range.
|
||||
uint64_t mSubsegmentIdx;
|
||||
// Index of the current byte range. Initialized to -1.
|
||||
int32_t mSubsegmentIdx;
|
||||
|
||||
// Ptr to the reader object for this |Representation|. Owned by state
|
||||
// machine.
|
||||
MediaDecoderReader* mReader;
|
||||
DASHRepReader* mReader;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
62
content/media/dash/DASHRepReader.h
Normal file
62
content/media/dash/DASHRepReader.h
Normal file
@ -0,0 +1,62 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
||||
|
||||
/* 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/. */
|
||||
|
||||
/* DASH - Dynamic Adaptive Streaming over HTTP
|
||||
*
|
||||
* DASH is an adaptive bitrate streaming technology where a multimedia file is
|
||||
* partitioned into one or more segments and delivered to a client using HTTP.
|
||||
*
|
||||
* see DASHDecoder.cpp for comments on DASH object interaction
|
||||
*/
|
||||
|
||||
#if !defined(DASHRepReader_h_)
|
||||
#define DASHRepReader_h_
|
||||
|
||||
#include "VideoUtils.h"
|
||||
#include "MediaDecoderReader.h"
|
||||
#include "DASHReader.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class DASHReader;
|
||||
|
||||
class DASHRepReader : public MediaDecoderReader
|
||||
{
|
||||
public:
|
||||
DASHRepReader(AbstractMediaDecoder* aDecoder)
|
||||
: MediaDecoderReader(aDecoder) { }
|
||||
virtual ~DASHRepReader() { }
|
||||
|
||||
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DASHRepReader)
|
||||
|
||||
virtual void SetMainReader(DASHReader *aMainReader) = 0;
|
||||
|
||||
// Sets range for initialization bytes; used by DASH.
|
||||
virtual void SetInitByteRange(MediaByteRange &aByteRange) = 0;
|
||||
|
||||
// Sets range for index frame bytes; used by DASH.
|
||||
virtual void SetIndexByteRange(MediaByteRange &aByteRange) = 0;
|
||||
|
||||
// Returns list of ranges for index frame start/end offsets. Used by DASH.
|
||||
virtual nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges) = 0;
|
||||
|
||||
// Returns true if the reader has reached a DASH switch access point.
|
||||
virtual bool HasReachedSubsegment(uint32_t aSubsegmentIndex) = 0;
|
||||
|
||||
// Requests a seek to the start of a particular DASH subsegment.
|
||||
virtual void RequestSeekToSubsegment(uint32_t aIdx) = 0;
|
||||
|
||||
// Reader should stop reading at the start of the specified subsegment, and
|
||||
// should prepare for the next reader to add data to the video queue.
|
||||
// Should be implemented by a sub-reader, e.g. |nsDASHWebMReader|.
|
||||
virtual void RequestSwitchAtSubsegment(int32_t aCluster,
|
||||
MediaDecoderReader* aNextReader) = 0;
|
||||
};
|
||||
|
||||
}// namespace mozilla
|
||||
|
||||
#endif /*DASHRepReader*/
|
@ -23,6 +23,7 @@ EXPORTS := \
|
||||
DASHDecoder.h \
|
||||
DASHRepDecoder.h \
|
||||
DASHReader.h \
|
||||
DASHRepReader.h \
|
||||
$(NULL)
|
||||
|
||||
CPPSRCS := \
|
||||
|
@ -103,7 +103,11 @@ static int64_t webm_tell(void *aUserData)
|
||||
}
|
||||
|
||||
WebMReader::WebMReader(AbstractMediaDecoder* aDecoder)
|
||||
#ifdef MOZ_DASH
|
||||
: DASHRepReader(aDecoder),
|
||||
#else
|
||||
: MediaDecoderReader(aDecoder),
|
||||
#endif
|
||||
mContext(nullptr),
|
||||
mPacketCount(0),
|
||||
mChannels(0),
|
||||
@ -113,6 +117,15 @@ WebMReader::WebMReader(AbstractMediaDecoder* aDecoder)
|
||||
mAudioFrames(0),
|
||||
mHasVideo(false),
|
||||
mHasAudio(false)
|
||||
#ifdef MOZ_DASH
|
||||
, mMainReader(nullptr),
|
||||
mSwitchingCluster(-1),
|
||||
mNextReader(nullptr),
|
||||
mSeekToCluster(-1),
|
||||
mCurrentOffset(-1),
|
||||
mPushVideoPacketToNextReader(false),
|
||||
mReachedSwitchAccessPoint(false)
|
||||
#endif
|
||||
{
|
||||
MOZ_COUNT_CTOR(WebMReader);
|
||||
// Zero these member vars to avoid crashes in VP8 destroy and Vorbis clear
|
||||
@ -204,7 +217,11 @@ nsresult WebMReader::ReadMetadata(VideoInfo* aInfo,
|
||||
io.seek = webm_seek;
|
||||
io.tell = webm_tell;
|
||||
io.userdata = mDecoder;
|
||||
#ifdef MOZ_DASH
|
||||
int64_t maxOffset = mInitByteRange.IsNull() ? -1 : mInitByteRange.mEnd;
|
||||
#else
|
||||
int64_t maxOffset = -1;
|
||||
#endif
|
||||
int r = nestegg_init(&mContext, io, nullptr, maxOffset);
|
||||
if (r == -1) {
|
||||
return NS_ERROR_FAILURE;
|
||||
@ -354,6 +371,7 @@ nsresult WebMReader::ReadMetadata(VideoInfo* aInfo,
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
// Byte range for cues has been specified; load them.
|
||||
if (!mCuesByteRange.IsNull()) {
|
||||
maxOffset = mCuesByteRange.mEnd;
|
||||
@ -386,12 +404,15 @@ nsresult WebMReader::ReadMetadata(VideoInfo* aInfo,
|
||||
}
|
||||
} while (!done);
|
||||
}
|
||||
#endif
|
||||
|
||||
*aInfo = mInfo;
|
||||
|
||||
*aTags = nullptr;
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
mDecoder->OnReadMetadataCompleted();
|
||||
#endif
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
@ -513,7 +534,7 @@ bool WebMReader::DecodeAudioPacket(nestegg_packet* aPacket, int64_t aOffset)
|
||||
};
|
||||
|
||||
total_frames += frames;
|
||||
mAudioQueue.Push(new AudioData(aOffset,
|
||||
AudioQueue().Push(new AudioData(aOffset,
|
||||
time.value(),
|
||||
duration.value(),
|
||||
frames,
|
||||
@ -530,6 +551,46 @@ bool WebMReader::DecodeAudioPacket(nestegg_packet* aPacket, int64_t aOffset)
|
||||
}
|
||||
|
||||
nsReturnRef<NesteggPacketHolder> WebMReader::NextPacket(TrackType aTrackType)
|
||||
#ifdef MOZ_DASH
|
||||
{
|
||||
nsAutoRef<NesteggPacketHolder> holder;
|
||||
// Get packet from next reader if we're at a switching point; most likely we
|
||||
// did not download the next packet for this reader's stream, so we have to
|
||||
// get it from the next one. Note: Switch to next reader only for video;
|
||||
// audio switching is not supported in the DASH-WebM On Demand profile.
|
||||
if (aTrackType == VIDEO &&
|
||||
(uint32_t)mSwitchingCluster < mClusterByteRanges.Length() &&
|
||||
mCurrentOffset == mClusterByteRanges[mSwitchingCluster].mStart) {
|
||||
|
||||
if (mVideoPackets.GetSize() > 0) {
|
||||
holder = NextPacketInternal(VIDEO);
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("WebMReader[%p] got packet from mVideoPackets @[%lld]",
|
||||
this, holder->mOffset));
|
||||
} else {
|
||||
mReachedSwitchAccessPoint = true;
|
||||
NS_ASSERTION(mNextReader,
|
||||
"Stream switch has been requested but mNextReader is null");
|
||||
holder = mNextReader->NextPacket(aTrackType);
|
||||
mPushVideoPacketToNextReader = true;
|
||||
// Reset for possible future switches.
|
||||
mSwitchingCluster = -1;
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("WebMReader[%p] got packet from mNextReader[%p] @[%lld]",
|
||||
this, mNextReader.get(), (holder ? holder->mOffset : 0)));
|
||||
}
|
||||
} else {
|
||||
holder = NextPacketInternal(aTrackType);
|
||||
if (holder) {
|
||||
mCurrentOffset = holder->mOffset;
|
||||
}
|
||||
}
|
||||
return holder.out();
|
||||
}
|
||||
|
||||
nsReturnRef<NesteggPacketHolder>
|
||||
WebMReader::NextPacketInternal(TrackType aTrackType)
|
||||
#endif
|
||||
{
|
||||
// The packet queue that packets will be pushed on if they
|
||||
// are not the type we are interested in.
|
||||
@ -598,7 +659,7 @@ bool WebMReader::DecodeAudioData()
|
||||
|
||||
nsAutoRef<NesteggPacketHolder> holder(NextPacket(AUDIO));
|
||||
if (!holder) {
|
||||
mAudioQueue.Finish();
|
||||
AudioQueue().Finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -617,7 +678,7 @@ bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
|
||||
nsAutoRef<NesteggPacketHolder> holder(NextPacket(VIDEO));
|
||||
if (!holder) {
|
||||
mVideoQueue.Finish();
|
||||
VideoQueue().Finish();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -652,7 +713,7 @@ bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
if (r == -1) {
|
||||
return false;
|
||||
}
|
||||
mVideoPackets.PushFront(next_holder.disown());
|
||||
PushVideoPacket(next_holder.disown());
|
||||
} else {
|
||||
ReentrantMonitorAutoEnter decoderMon(mDecoder->GetReentrantMonitor());
|
||||
int64_t endTime = mDecoder->GetEndMediaTime();
|
||||
@ -752,19 +813,37 @@ bool WebMReader::DecodeVideoFrame(bool &aKeyframeSkip,
|
||||
decoded++;
|
||||
NS_ASSERTION(decoded <= parsed,
|
||||
"Expect only 1 frame per chunk per packet in WebM...");
|
||||
mVideoQueue.Push(v);
|
||||
VideoQueue().Push(v);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
WebMReader::PushVideoPacket(NesteggPacketHolder* aItem)
|
||||
{
|
||||
#ifdef MOZ_DASH
|
||||
if (mPushVideoPacketToNextReader) {
|
||||
NS_ASSERTION(mNextReader,
|
||||
"Stream switch has been requested but mNextReader is null");
|
||||
mNextReader->mVideoPackets.PushFront(aItem);
|
||||
mPushVideoPacketToNextReader = false;
|
||||
} else {
|
||||
#endif
|
||||
mVideoPackets.PushFront(aItem);
|
||||
#ifdef MOZ_DASH
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
nsresult WebMReader::Seek(int64_t aTarget, int64_t aStartTime, int64_t aEndTime,
|
||||
int64_t aCurrentTime)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
|
||||
LOG(PR_LOG_DEBUG, ("%p About to seek to %fs", mDecoder, aTarget/1000000.0));
|
||||
LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: About to seek to %fs",
|
||||
this, mDecoder, aTarget/1000000.0));
|
||||
if (NS_FAILED(ResetDecode())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
@ -836,8 +915,9 @@ void WebMReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_
|
||||
mBufferedState->NotifyDataArrived(aBuffer, aLength, aOffset);
|
||||
}
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
nsresult
|
||||
WebMReader::GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges)
|
||||
WebMReader::GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
|
||||
{
|
||||
NS_ENSURE_TRUE(mContext, NS_ERROR_NULL_POINTER);
|
||||
NS_ENSURE_TRUE(aByteRanges.IsEmpty(), NS_ERROR_ALREADY_INITIALIZED);
|
||||
@ -849,6 +929,97 @@ WebMReader::GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
WebMReader::RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
|
||||
MediaDecoderReader* aNextReader)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
|
||||
"Should be on main thread or decode thread.");
|
||||
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
|
||||
// Only allow one switch at a time; ignore if one is already requested.
|
||||
if (mSwitchingCluster != -1) {
|
||||
return;
|
||||
}
|
||||
NS_ENSURE_TRUE((uint32_t)aSubsegmentIdx < mClusterByteRanges.Length(), );
|
||||
mSwitchingCluster = aSubsegmentIdx;
|
||||
NS_ENSURE_TRUE(aNextReader != this, );
|
||||
mNextReader = static_cast<WebMReader*>(aNextReader);
|
||||
}
|
||||
|
||||
void
|
||||
WebMReader::RequestSeekToSubsegment(uint32_t aIdx)
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread() || mDecoder->OnDecodeThread(),
|
||||
"Should be on main thread or decode thread.");
|
||||
NS_ASSERTION(mDecoder, "decoder should not be null!");
|
||||
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
|
||||
|
||||
// Don't seek if we're about to switch to another reader.
|
||||
if (mSwitchingCluster != -1) {
|
||||
return;
|
||||
}
|
||||
// Only allow seeking if a request was not already made.
|
||||
if (mSeekToCluster != -1) {
|
||||
return;
|
||||
}
|
||||
NS_ENSURE_TRUE(aIdx < mClusterByteRanges.Length(), );
|
||||
mSeekToCluster = aIdx;
|
||||
|
||||
// XXX Hack to get the resource to seek to the correct offset if the decode
|
||||
// thread is in shutdown, e.g. if the video is not autoplay.
|
||||
if (mDecoder->IsShutdown()) {
|
||||
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
|
||||
mDecoder->GetResource()->Seek(PR_SEEK_SET,
|
||||
mClusterByteRanges[mSeekToCluster].mStart);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebMReader::PrepareToDecode()
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
if (mSeekToCluster != -1) {
|
||||
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
|
||||
SeekToCluster(mSeekToCluster);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
WebMReader::SeekToCluster(uint32_t aIdx)
|
||||
{
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
NS_ASSERTION(0 <= mSeekToCluster, "mSeekToCluster should be set.");
|
||||
NS_ENSURE_TRUE(aIdx < mClusterByteRanges.Length(), );
|
||||
LOG(PR_LOG_DEBUG, ("Reader [%p] for Decoder [%p]: seeking to "
|
||||
"subsegment [%lld] at offset [%lld]",
|
||||
this, mDecoder, aIdx, mClusterByteRanges[aIdx].mStart));
|
||||
int r = nestegg_offset_seek(mContext, mClusterByteRanges[aIdx].mStart);
|
||||
NS_ENSURE_TRUE(r == 0, );
|
||||
mSeekToCluster = -1;
|
||||
}
|
||||
|
||||
bool
|
||||
WebMReader::HasReachedSubsegment(uint32_t aSubsegmentIndex)
|
||||
{
|
||||
NS_ASSERTION(mDecoder, "Decoder is null.");
|
||||
NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
|
||||
NS_ENSURE_TRUE(aSubsegmentIndex < mClusterByteRanges.Length(), false);
|
||||
|
||||
NS_ASSERTION(mDecoder->GetResource(), "Decoder has no media resource.");
|
||||
if (mReachedSwitchAccessPoint) {
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("Reader [%p] for Decoder [%p]: reached switching offset [%lld] = "
|
||||
"mClusterByteRanges[%d].mStart[%lld]",
|
||||
this, mDecoder, mCurrentOffset, aSubsegmentIndex,
|
||||
mClusterByteRanges[aSubsegmentIndex].mStart));
|
||||
mReachedSwitchAccessPoint = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif /* MOZ_DASH */
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
|
@ -22,6 +22,10 @@
|
||||
#include "vorbis/codec.h"
|
||||
#endif
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
#include "DASHRepReader.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
class WebMBufferedState;
|
||||
@ -97,7 +101,11 @@ class WebMPacketQueue : private nsDeque {
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
class WebMReader : public DASHRepReader
|
||||
#else
|
||||
class WebMReader : public MediaDecoderReader
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
WebMReader(AbstractMediaDecoder* aDecoder);
|
||||
@ -136,20 +144,75 @@ public:
|
||||
virtual nsresult GetBuffered(nsTimeRanges* aBuffered, int64_t aStartTime);
|
||||
virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
virtual void SetMainReader(DASHReader *aMainReader) MOZ_OVERRIDE {
|
||||
NS_ASSERTION(aMainReader, "aMainReader is null.");
|
||||
mMainReader = aMainReader;
|
||||
}
|
||||
|
||||
// Called by |DASHReader| on the decode thread so that this reader will
|
||||
// start reading at the appropriate subsegment/cluster. If this is not the
|
||||
// current reader and a switch was previously requested, then it will seek to
|
||||
// starting offset of the subsegment at which it is supposed to switch.
|
||||
// Called on the decode thread, enters the decode monitor.
|
||||
void PrepareToDecode() MOZ_OVERRIDE;
|
||||
|
||||
// Returns a reference to the audio/video queue of the main reader.
|
||||
// Allows for a single audio/video queue to be shared among multiple
|
||||
// |WebMReader|s.
|
||||
MediaQueue<AudioData>& AudioQueue() MOZ_OVERRIDE {
|
||||
if (mMainReader) {
|
||||
return mMainReader->AudioQueue();
|
||||
} else {
|
||||
return MediaDecoderReader::AudioQueue();
|
||||
}
|
||||
}
|
||||
|
||||
MediaQueue<VideoData>& VideoQueue() MOZ_OVERRIDE {
|
||||
if (mMainReader) {
|
||||
return mMainReader->VideoQueue();
|
||||
} else {
|
||||
return MediaDecoderReader::VideoQueue();
|
||||
}
|
||||
}
|
||||
|
||||
// Sets byte range for initialization (EBML); used by DASH.
|
||||
void SetInitByteRange(MediaByteRange &aByteRange) {
|
||||
void SetInitByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
|
||||
mInitByteRange = aByteRange;
|
||||
}
|
||||
|
||||
// Sets byte range for cue points, i.e. cluster offsets; used by DASH.
|
||||
void SetIndexByteRange(MediaByteRange &aByteRange) {
|
||||
void SetIndexByteRange(MediaByteRange &aByteRange) MOZ_OVERRIDE {
|
||||
mCuesByteRange = aByteRange;
|
||||
}
|
||||
|
||||
// Returns list of ranges for cluster start and end offsets.
|
||||
nsresult GetIndexByteRanges(nsTArray<MediaByteRange>& aByteRanges);
|
||||
nsresult GetSubsegmentByteRanges(nsTArray<MediaByteRange>& aByteRanges)
|
||||
MOZ_OVERRIDE;
|
||||
|
||||
private:
|
||||
// Called by |DASHReader|::|PossiblySwitchVideoReaders| to check if this
|
||||
// reader has reached a switch access point and it's ok to switch readers.
|
||||
// Called on the decode thread.
|
||||
bool HasReachedSubsegment(uint32_t aSubsegmentIndex) MOZ_OVERRIDE;
|
||||
|
||||
// Requests that this reader seek to the specified subsegment. Seek will
|
||||
// happen when |PrepareDecodeVideoFrame| is called on the decode
|
||||
// thread.
|
||||
// Called on the main thread or decoder thread. Decode monitor must be held.
|
||||
void RequestSeekToSubsegment(uint32_t aIdx) MOZ_OVERRIDE;
|
||||
|
||||
// Requests that this reader switch to |aNextReader| at the start of the
|
||||
// specified subsegment. This is the reader to switch FROM.
|
||||
// Called on the main thread or decoder thread. Decode monitor must be held.
|
||||
void RequestSwitchAtSubsegment(int32_t aSubsegmentIdx,
|
||||
MediaDecoderReader* aNextReader) MOZ_OVERRIDE;
|
||||
|
||||
// Seeks to the beginning of the specified cluster. Called on the decode
|
||||
// thread.
|
||||
void SeekToCluster(uint32_t aIdx);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// Value passed to NextPacket to determine if we are reading a video or an
|
||||
// audio packet.
|
||||
enum TrackType {
|
||||
@ -160,8 +223,19 @@ private:
|
||||
// Read a packet from the nestegg file. Returns NULL if all packets for
|
||||
// the particular track have been read. Pass VIDEO or AUDIO to indicate the
|
||||
// type of the packet we want to read.
|
||||
#ifdef MOZ_DASH
|
||||
nsReturnRef<NesteggPacketHolder> NextPacketInternal(TrackType aTrackType);
|
||||
|
||||
// Read a packet from the nestegg file. Returns NULL if all packets for
|
||||
// the particular track have been read. Pass VIDEO or AUDIO to indicate the
|
||||
// type of the packet we want to read. If the reader reaches a switch access
|
||||
// point, this function will get a packet from |mNextReader|.
|
||||
#endif
|
||||
nsReturnRef<NesteggPacketHolder> NextPacket(TrackType aTrackType);
|
||||
|
||||
// Pushes a packet to the front of the video packet queue.
|
||||
virtual void PushVideoPacket(NesteggPacketHolder* aItem);
|
||||
|
||||
// Returns an initialized ogg packet with data obtained from the WebM container.
|
||||
ogg_packet InitOggPacket(unsigned char* aData,
|
||||
size_t aLength,
|
||||
@ -227,6 +301,7 @@ private:
|
||||
bool mHasVideo;
|
||||
bool mHasAudio;
|
||||
|
||||
#ifdef MOZ_DASH
|
||||
// Byte range for initialisation data; e.g. specified in DASH manifest.
|
||||
MediaByteRange mInitByteRange;
|
||||
|
||||
@ -235,6 +310,40 @@ private:
|
||||
|
||||
// Byte ranges for clusters; set internally, derived from cues.
|
||||
nsTArray<MediaByteRange> mClusterByteRanges;
|
||||
|
||||
// Pointer to the main |DASHReader|. Set in the constructor.
|
||||
DASHReader* mMainReader;
|
||||
|
||||
// Index of the cluster to switch to. Monitor must be entered for write
|
||||
// access on all threads, read access off the decode thread.
|
||||
int32_t mSwitchingCluster;
|
||||
|
||||
// Pointer to the next reader. Used in |NextPacket| and |PushVideoPacket| at
|
||||
// switch access points. Monitor must be entered for write access on all
|
||||
// threads, read access off the decode thread.
|
||||
nsRefPtr<WebMReader> mNextReader;
|
||||
|
||||
// Index of the cluster to seek to for a DASH stream request. Monitor must be
|
||||
// entered for write access on all threads, read access off the decode
|
||||
// thread.
|
||||
int32_t mSeekToCluster;
|
||||
|
||||
// Current end offset of the last packet read in |NextPacket|. Used to check
|
||||
// if the reader reached the switch access point. Accessed on the decode
|
||||
// thread only.
|
||||
int64_t mCurrentOffset;
|
||||
|
||||
// Set in |NextPacket| if we read a packet from the next reader. If true in
|
||||
// |PushVideoPacket|, we will push the packet onto the next reader's
|
||||
// video packet queue (not video data queue!). Accessed on decode thread
|
||||
// only.
|
||||
bool mPushVideoPacketToNextReader;
|
||||
|
||||
// Indicates if the reader has reached a switch access point. Set in
|
||||
// |NextPacket| and read in |HasReachedSubsegment|. Accessed on
|
||||
// decode thread only.
|
||||
bool mReachedSwitchAccessPoint;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
@ -72,7 +72,7 @@ XBLFinalize(JSFreeOp *fop, JSObject *obj)
|
||||
{
|
||||
nsXBLDocumentInfo* docInfo =
|
||||
static_cast<nsXBLDocumentInfo*>(::JS_GetPrivate(obj));
|
||||
NS_RELEASE(docInfo);
|
||||
xpc::DeferredRelease(static_cast<nsIScriptGlobalObjectOwner*>(docInfo));
|
||||
|
||||
nsXBLJSClass* c = static_cast<nsXBLJSClass*>(::JS_GetClass(obj));
|
||||
c->Drop();
|
||||
|
@ -390,16 +390,6 @@ nsXULDocument::ResetToURI(nsIURI* aURI, nsILoadGroup* aLoadGroup,
|
||||
NS_NOTREACHED("ResetToURI");
|
||||
}
|
||||
|
||||
// Override the nsDocument.cpp method to keep from returning the
|
||||
// "cached XUL" type which is completely internal and may confuse
|
||||
// people
|
||||
NS_IMETHODIMP
|
||||
nsXULDocument::GetContentType(nsAString& aContentType)
|
||||
{
|
||||
aContentType.AssignLiteral("application/vnd.mozilla.xul+xml");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsXULDocument::SetContentType(const nsAString& aContentType)
|
||||
{
|
||||
@ -1882,11 +1872,11 @@ nsXULDocument::RemoveElementFromRefMap(Element* aElement)
|
||||
// nsIDOMNode interface
|
||||
//
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsXULDocument::CloneNode(bool aDeep, uint8_t aOptionalArgc, nsIDOMNode** aReturn)
|
||||
nsresult
|
||||
nsXULDocument::Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const
|
||||
{
|
||||
// We don't allow cloning of a document
|
||||
*aReturn = nullptr;
|
||||
// We don't allow cloning of a XUL document
|
||||
*aResult = nullptr;
|
||||
return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
|
||||
}
|
||||
|
||||
|
@ -132,18 +132,16 @@ public:
|
||||
NS_IMETHOD OnPrototypeLoadDone(bool aResumeWalk);
|
||||
bool OnDocumentParserError();
|
||||
|
||||
// nsIDOMNode interface overrides
|
||||
NS_IMETHOD CloneNode(bool deep, uint8_t aOptionalArgc, nsIDOMNode **_retval)
|
||||
MOZ_OVERRIDE;
|
||||
// nsINode interface overrides
|
||||
virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
|
||||
|
||||
// nsIDOMDocument
|
||||
NS_IMETHOD GetContentType(nsAString& aContentType);
|
||||
// nsIDOMNode interface
|
||||
NS_FORWARD_NSIDOMNODE_TO_NSINODE
|
||||
|
||||
// nsIDOMDocument interface
|
||||
NS_FORWARD_NSIDOMDOCUMENT(nsXMLDocument::)
|
||||
|
||||
// nsDocument interface overrides
|
||||
NS_IMETHOD GetElementById(const nsAString& aId, nsIDOMElement** aReturn)
|
||||
{
|
||||
return nsDocument::GetElementById(aId, aReturn);
|
||||
}
|
||||
virtual mozilla::dom::Element* GetElementById(const nsAString & elementId);
|
||||
|
||||
// nsIDOMXULDocument interface
|
||||
|
@ -5211,7 +5211,7 @@ nsDocShell::SetIsActive(bool aIsActive)
|
||||
win->SetIsBackground(!aIsActive);
|
||||
nsCOMPtr<nsIDocument> doc = do_QueryInterface(win->GetExtantDocument());
|
||||
if (doc) {
|
||||
doc->PostVisibilityUpdateEvent();
|
||||
doc->UpdateVisibilityState(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,6 +96,7 @@ MOCHITEST_CHROME_FILES = \
|
||||
test_bug789773.xul \
|
||||
test_bug754029.xul \
|
||||
bug754029_window.xul \
|
||||
test_bug818371.xul \
|
||||
docshell_helpers.js \
|
||||
generic.html \
|
||||
$(NULL)
|
||||
|
@ -39,7 +39,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=789773
|
||||
wp.DOMWindow; // Force the lazy creation of a DOM window.
|
||||
calledListenerForBrowserXUL = true;
|
||||
}
|
||||
if (/aboutHome.xhtml/.test(req.name) && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP))
|
||||
if (/mozilla.xhtml/.test(req.name) && (stateFlags & Ci.nsIWebProgressListener.STATE_STOP))
|
||||
finishTest();
|
||||
},
|
||||
QueryInterface: function(iid) {
|
||||
@ -55,7 +55,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=789773
|
||||
webProgress.addProgressListener(testProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
|
||||
|
||||
// Open the window.
|
||||
var popup = window.open("about:home", "_blank", "width=640,height=400");
|
||||
var popup = window.open("about:mozilla", "_blank", "width=640,height=400");
|
||||
|
||||
// Wait for the window to load.
|
||||
function finishTest() {
|
||||
|
43
docshell/test/chrome/test_bug818371.xul
Normal file
43
docshell/test/chrome/test_bug818371.xul
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
|
||||
<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=818371
|
||||
-->
|
||||
<window title="Mozilla Bug 818371"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
|
||||
|
||||
<!-- test results are displayed in the html:body -->
|
||||
<body xmlns="http://www.w3.org/1999/xhtml">
|
||||
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=818371"
|
||||
target="_blank">Mozilla Bug 818371</a>
|
||||
</body>
|
||||
|
||||
<browser id="b" src="data:text/html,<iframe></iframe>"></browser>
|
||||
|
||||
<!-- test code goes here -->
|
||||
<script type="application/javascript">
|
||||
<![CDATA[
|
||||
/** Test for Bug 818371 **/
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
addLoadEvent(function() {
|
||||
function listener(e) {
|
||||
ok(e.target.hidden, "Document should now be hidden");
|
||||
ok(e.target.defaultView.frames[0].document.hidden,
|
||||
"Subdocument should now be hidden");
|
||||
e.target.removeEventListener("visibilitychange", listener);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
var doc = frames[0].document;
|
||||
ok(!doc.hidden, "Document should be visible");
|
||||
ok(!frames[0].frames[0].document.hidden,
|
||||
"Subdocument should now be hidden");
|
||||
doc.addEventListener("visibilitychange", listener);
|
||||
$("b").docShell.isActive = false;
|
||||
});
|
||||
|
||||
]]>
|
||||
</script>
|
||||
</window>
|
@ -307,6 +307,16 @@ this.DOMApplicationRegistry = {
|
||||
this.installSystemApps(onAppsLoaded);
|
||||
else
|
||||
onAppsLoaded();
|
||||
|
||||
// XXX: To be removed as soon as the app:// protocol is remoted.
|
||||
// See Bug 819061
|
||||
let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
dir.initWithPath("/data");
|
||||
dir.permissions = parseInt("755", 8);
|
||||
dir.append("local");
|
||||
dir.permissions = parseInt("755", 8);
|
||||
dir.append("webapps");
|
||||
dir.permissions = parseInt("755", 8);
|
||||
#else
|
||||
onAppsLoaded();
|
||||
#endif
|
||||
|
116
dom/audiochannel/AudioChannelAgent.cpp
Normal file
116
dom/audiochannel/AudioChannelAgent.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "AudioChannelAgent.h"
|
||||
#include "AudioChannelCommon.h"
|
||||
#include "AudioChannelService.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
||||
NS_IMPL_ISUPPORTS1(AudioChannelAgent, nsIAudioChannelAgent)
|
||||
|
||||
static nsRefPtr<AudioChannelService> gAudioChannelService;
|
||||
|
||||
AudioChannelAgent::AudioChannelAgent()
|
||||
: mCallback(nullptr)
|
||||
, mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
|
||||
, mIsRegToService(false)
|
||||
, mVisible(true)
|
||||
{
|
||||
gAudioChannelService = AudioChannelService::GetAudioChannelService();
|
||||
}
|
||||
|
||||
AudioChannelAgent::~AudioChannelAgent()
|
||||
{
|
||||
gAudioChannelService = nullptr;
|
||||
}
|
||||
|
||||
/* readonly attribute long audioChannelType; */
|
||||
NS_IMETHODIMP AudioChannelAgent::GetAudioChannelType(int32_t *aAudioChannelType)
|
||||
{
|
||||
*aAudioChannelType = mAudioChannelType;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* boolean init (in long channelType); */
|
||||
NS_IMETHODIMP AudioChannelAgent::Init(int32_t channelType, nsIAudioChannelAgentCallback *callback)
|
||||
{
|
||||
// We syncd the enum of channel type between nsIAudioChannelAgent.idl and
|
||||
// AudioChannelCommon.h the same.
|
||||
MOZ_STATIC_ASSERT(static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_NORMAL) ==
|
||||
AUDIO_CHANNEL_NORMAL &&
|
||||
static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_CONTENT) ==
|
||||
AUDIO_CHANNEL_CONTENT &&
|
||||
static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_NOTIFICATION) ==
|
||||
AUDIO_CHANNEL_NOTIFICATION &&
|
||||
static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_ALARM) ==
|
||||
AUDIO_CHANNEL_ALARM &&
|
||||
static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_TELEPHONY) ==
|
||||
AUDIO_CHANNEL_TELEPHONY &&
|
||||
static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_RINGER) ==
|
||||
AUDIO_CHANNEL_RINGER &&
|
||||
static_cast<AudioChannelType>(AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION) ==
|
||||
AUDIO_CHANNEL_PUBLICNOTIFICATION,
|
||||
"Enum of channel on nsIAudioChannelAgent.idl should be the same with AudioChannelCommon.h");
|
||||
|
||||
if (mAudioChannelType != AUDIO_AGENT_CHANNEL_ERROR ||
|
||||
channelType > AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION ||
|
||||
channelType < AUDIO_AGENT_CHANNEL_NORMAL) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
mAudioChannelType = channelType;
|
||||
mCallback = callback;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* boolean startPlaying (); */
|
||||
NS_IMETHODIMP AudioChannelAgent::StartPlaying(bool *_retval)
|
||||
{
|
||||
if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
|
||||
gAudioChannelService == nullptr) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
gAudioChannelService->RegisterAudioChannelAgent(this,
|
||||
static_cast<AudioChannelType>(mAudioChannelType));
|
||||
*_retval = !gAudioChannelService->GetMuted(static_cast<AudioChannelType>(mAudioChannelType), !mVisible);
|
||||
mIsRegToService = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* void stopPlaying (); */
|
||||
NS_IMETHODIMP AudioChannelAgent::StopPlaying(void)
|
||||
{
|
||||
if (mAudioChannelType == AUDIO_AGENT_CHANNEL_ERROR ||
|
||||
mIsRegToService == false) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
gAudioChannelService->UnregisterAudioChannelAgent(this);
|
||||
mIsRegToService = false;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* void setVisibilityState (in boolean visible); */
|
||||
NS_IMETHODIMP AudioChannelAgent::SetVisibilityState(bool visible)
|
||||
{
|
||||
bool oldVisibility = mVisible;
|
||||
|
||||
mVisible = visible;
|
||||
if (mIsRegToService && oldVisibility != mVisible && mCallback != nullptr) {
|
||||
mCallback->CanPlayChanged(!gAudioChannelService->GetMuted(static_cast<AudioChannelType>(mAudioChannelType),
|
||||
!mVisible));
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void AudioChannelAgent::NotifyAudioChannelStateChanged()
|
||||
{
|
||||
if (mCallback != nullptr) {
|
||||
mCallback->CanPlayChanged(!gAudioChannelService->GetMuted(static_cast<AudioChannelType>(mAudioChannelType),
|
||||
!mVisible));
|
||||
}
|
||||
}
|
||||
|
42
dom/audiochannel/AudioChannelAgent.h
Normal file
42
dom/audiochannel/AudioChannelAgent.h
Normal file
@ -0,0 +1,42 @@
|
||||
/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
|
||||
/* vim: set ts=2 et sw=2 tw=40: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_audio_channel_agent_h__
|
||||
#define mozilla_dom_audio_channel_agent_h__
|
||||
|
||||
#include "nsIAudioChannelAgent.h"
|
||||
#include "nsCOMPtr.h"
|
||||
|
||||
#define NS_AUDIOCHANNELAGENT_CONTRACTID "@mozilla.org/audiochannelagent;1"
|
||||
// f27688e2-3dd7-11e2-904e-10bf48d64bd4
|
||||
#define NS_AUDIOCHANNELAGENT_CID {0xf27688e2, 0x3dd7, 0x11e2, \
|
||||
{0x90, 0x4e, 0x10, 0xbf, 0x48, 0xd6, 0x4b, 0xd4}}
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
/* Header file */
|
||||
class AudioChannelAgent : public nsIAudioChannelAgent
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIAUDIOCHANNELAGENT
|
||||
|
||||
AudioChannelAgent();
|
||||
virtual void NotifyAudioChannelStateChanged();
|
||||
|
||||
private:
|
||||
virtual ~AudioChannelAgent();
|
||||
nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
|
||||
int32_t mAudioChannelType;
|
||||
bool mIsRegToService;
|
||||
bool mVisible;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
#endif
|
||||
|
@ -18,6 +18,7 @@ enum AudioChannelType {
|
||||
AUDIO_CHANNEL_NOTIFICATION,
|
||||
AUDIO_CHANNEL_ALARM,
|
||||
AUDIO_CHANNEL_TELEPHONY,
|
||||
AUDIO_CHANNEL_RINGER,
|
||||
AUDIO_CHANNEL_PUBLICNOTIFICATION,
|
||||
AUDIO_CHANNEL_LAST
|
||||
};
|
||||
|
@ -20,6 +20,9 @@
|
||||
|
||||
#include "nsThreadUtils.h"
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
#include "nsIAudioManager.h"
|
||||
#endif
|
||||
using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
|
||||
@ -75,7 +78,7 @@ AudioChannelService::AudioChannelService()
|
||||
}
|
||||
|
||||
// Creation of the hash table.
|
||||
mMediaElements.Init();
|
||||
mAgents.Init();
|
||||
}
|
||||
|
||||
AudioChannelService::~AudioChannelService()
|
||||
@ -84,10 +87,10 @@ AudioChannelService::~AudioChannelService()
|
||||
}
|
||||
|
||||
void
|
||||
AudioChannelService::RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
|
||||
AudioChannelService::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
|
||||
AudioChannelType aType)
|
||||
{
|
||||
mMediaElements.Put(aMediaElement, aType);
|
||||
mAgents.Put(aAgent, aType);
|
||||
RegisterType(aType);
|
||||
}
|
||||
|
||||
@ -97,19 +100,19 @@ AudioChannelService::RegisterType(AudioChannelType aType)
|
||||
mChannelCounters[aType]++;
|
||||
|
||||
// In order to avoid race conditions, it's safer to notify any existing
|
||||
// media element any time a new one is registered.
|
||||
// agent any time a new one is registered.
|
||||
Notify();
|
||||
}
|
||||
|
||||
void
|
||||
AudioChannelService::UnregisterMediaElement(nsHTMLMediaElement* aMediaElement)
|
||||
AudioChannelService::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
|
||||
{
|
||||
AudioChannelType type;
|
||||
if (!mMediaElements.Get(aMediaElement, &type)) {
|
||||
if (!mAgents.Get(aAgent, &type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
mMediaElements.Remove(aMediaElement);
|
||||
mAgents.Remove(aAgent);
|
||||
UnregisterType(type);
|
||||
}
|
||||
|
||||
@ -120,7 +123,7 @@ AudioChannelService::UnregisterType(AudioChannelType aType)
|
||||
MOZ_ASSERT(mChannelCounters[aType] >= 0);
|
||||
|
||||
// In order to avoid race conditions, it's safer to notify any existing
|
||||
// media element any time a new one is registered.
|
||||
// agent any time a new one is registered.
|
||||
Notify();
|
||||
}
|
||||
|
||||
@ -142,6 +145,7 @@ AudioChannelService::GetMuted(AudioChannelType aType, bool aElementHidden)
|
||||
case AUDIO_CHANNEL_NOTIFICATION:
|
||||
case AUDIO_CHANNEL_ALARM:
|
||||
case AUDIO_CHANNEL_TELEPHONY:
|
||||
case AUDIO_CHANNEL_RINGER:
|
||||
case AUDIO_CHANNEL_PUBLICNOTIFICATION:
|
||||
// Nothing to do
|
||||
break;
|
||||
@ -161,12 +165,16 @@ AudioChannelService::GetMuted(AudioChannelType aType, bool aElementHidden)
|
||||
muted = !!mChannelCounters[AUDIO_CHANNEL_NOTIFICATION] ||
|
||||
!!mChannelCounters[AUDIO_CHANNEL_ALARM] ||
|
||||
!!mChannelCounters[AUDIO_CHANNEL_TELEPHONY] ||
|
||||
!!mChannelCounters[AUDIO_CHANNEL_RINGER] ||
|
||||
!!mChannelCounters[AUDIO_CHANNEL_PUBLICNOTIFICATION];
|
||||
break;
|
||||
|
||||
case AUDIO_CHANNEL_NOTIFICATION:
|
||||
case AUDIO_CHANNEL_ALARM:
|
||||
case AUDIO_CHANNEL_TELEPHONY:
|
||||
case AUDIO_CHANNEL_RINGER:
|
||||
muted = ChannelsActiveWithHigherPriorityThan(aType);
|
||||
break;
|
||||
|
||||
case AUDIO_CHANNEL_PUBLICNOTIFICATION:
|
||||
break;
|
||||
@ -205,11 +213,11 @@ AudioChannelService::GetMuted(AudioChannelType aType, bool aElementHidden)
|
||||
|
||||
|
||||
static PLDHashOperator
|
||||
NotifyEnumerator(nsHTMLMediaElement* aElement,
|
||||
NotifyEnumerator(AudioChannelAgent* aAgent,
|
||||
AudioChannelType aType, void* aData)
|
||||
{
|
||||
if (aElement) {
|
||||
aElement->NotifyAudioChannelStateChanged();
|
||||
if (aAgent) {
|
||||
aAgent->NotifyAudioChannelStateChanged();
|
||||
}
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
@ -219,8 +227,8 @@ AudioChannelService::Notify()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Notify any media element for the main process.
|
||||
mMediaElements.EnumerateRead(NotifyEnumerator, nullptr);
|
||||
// Notify any agent for the main process.
|
||||
mAgents.EnumerateRead(NotifyEnumerator, nullptr);
|
||||
|
||||
// Notify for the child processes.
|
||||
nsTArray<ContentParent*> children;
|
||||
@ -259,6 +267,7 @@ AudioChannelService::ChannelName(AudioChannelType aType)
|
||||
{ AUDIO_CHANNEL_NOTIFICATION, "notification" },
|
||||
{ AUDIO_CHANNEL_ALARM, "alarm" },
|
||||
{ AUDIO_CHANNEL_TELEPHONY, "telephony" },
|
||||
{ AUDIO_CHANNEL_RINGER, "ringer" },
|
||||
{ AUDIO_CHANNEL_PUBLICNOTIFICATION, "publicnotification" },
|
||||
{ -1, "unknown" }
|
||||
};
|
||||
@ -273,3 +282,18 @@ AudioChannelService::ChannelName(AudioChannelType aType)
|
||||
NS_NOTREACHED("Execution should not reach here!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
void
|
||||
AudioChannelService::SetPhoneInCall(bool aActive)
|
||||
{
|
||||
//while ring tone and in-call mode, mute media element
|
||||
if (aActive) {
|
||||
mChannelCounters[AUDIO_CHANNEL_TELEPHONY] = 1;
|
||||
} else {
|
||||
mChannelCounters[AUDIO_CHANNEL_TELEPHONY] = 0;
|
||||
}
|
||||
Notify();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -11,7 +11,8 @@
|
||||
#include "nsISupports.h"
|
||||
|
||||
#include "AudioChannelCommon.h"
|
||||
#include "nsHTMLMediaElement.h"
|
||||
#include "AudioChannelAgent.h"
|
||||
#include "nsDataHashtable.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -34,23 +35,30 @@ public:
|
||||
static void Shutdown();
|
||||
|
||||
/**
|
||||
* Any MediaElement that starts playing should register itself to
|
||||
* Any audio channel agent that starts playing should register itself to
|
||||
* this service, sharing the AudioChannelType.
|
||||
*/
|
||||
virtual void RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
|
||||
AudioChannelType aType);
|
||||
virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
|
||||
AudioChannelType aType);
|
||||
|
||||
/**
|
||||
* Any MediaElement that stops playing should unregister itself to
|
||||
* Any audio channel agent that stops playing should unregister itself to
|
||||
* this service.
|
||||
*/
|
||||
virtual void UnregisterMediaElement(nsHTMLMediaElement* aMediaElement);
|
||||
virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
|
||||
|
||||
/**
|
||||
* Return true if this type should be muted.
|
||||
*/
|
||||
virtual bool GetMuted(AudioChannelType aType, bool aElementHidden);
|
||||
|
||||
/**
|
||||
* Sync the phone status with telephony
|
||||
*/
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
void SetPhoneInCall(bool aActive);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
void Notify();
|
||||
|
||||
@ -65,7 +73,7 @@ protected:
|
||||
|
||||
const char* ChannelName(AudioChannelType aType);
|
||||
|
||||
nsDataHashtable< nsPtrHashKey<nsHTMLMediaElement>, AudioChannelType > mMediaElements;
|
||||
nsDataHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelType > mAgents;
|
||||
|
||||
int32_t* mChannelCounters;
|
||||
|
||||
|
@ -74,10 +74,10 @@ AudioChannelServiceChild::GetMuted(AudioChannelType aType, bool aMozHidden)
|
||||
}
|
||||
|
||||
void
|
||||
AudioChannelServiceChild::RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
|
||||
AudioChannelServiceChild::RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
|
||||
AudioChannelType aType)
|
||||
{
|
||||
AudioChannelService::RegisterMediaElement(aMediaElement, aType);
|
||||
AudioChannelService::RegisterAudioChannelAgent(aAgent, aType);
|
||||
|
||||
ContentChild *cc = ContentChild::GetSingleton();
|
||||
if (cc) {
|
||||
@ -86,14 +86,14 @@ AudioChannelServiceChild::RegisterMediaElement(nsHTMLMediaElement* aMediaElement
|
||||
}
|
||||
|
||||
void
|
||||
AudioChannelServiceChild::UnregisterMediaElement(nsHTMLMediaElement* aMediaElement)
|
||||
AudioChannelServiceChild::UnregisterAudioChannelAgent(AudioChannelAgent* aAgent)
|
||||
{
|
||||
AudioChannelType type;
|
||||
if (!mMediaElements.Get(aMediaElement, &type)) {
|
||||
if (!mAgents.Get(aAgent, &type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AudioChannelService::UnregisterMediaElement(aMediaElement);
|
||||
AudioChannelService::UnregisterAudioChannelAgent(aAgent);
|
||||
|
||||
ContentChild *cc = ContentChild::GetSingleton();
|
||||
if (cc) {
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
#include "AudioChannelService.h"
|
||||
#include "AudioChannelCommon.h"
|
||||
#include "nsHTMLMediaElement.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -30,9 +29,9 @@ public:
|
||||
|
||||
static void Shutdown();
|
||||
|
||||
virtual void RegisterMediaElement(nsHTMLMediaElement* aMediaElement,
|
||||
AudioChannelType aType);
|
||||
virtual void UnregisterMediaElement(nsHTMLMediaElement* aMediaElement);
|
||||
virtual void RegisterAudioChannelAgent(AudioChannelAgent* aAgent,
|
||||
AudioChannelType aType);
|
||||
virtual void UnregisterAudioChannelAgent(AudioChannelAgent* aAgent);
|
||||
|
||||
/**
|
||||
* Return true if this type + this mozHidden should be muted.
|
||||
|
@ -33,11 +33,17 @@ EXPORTS_NAMESPACES = \
|
||||
|
||||
EXPORTS = AudioChannelService.h \
|
||||
AudioChannelServiceChild.h \
|
||||
AudioChannelCommon.h
|
||||
AudioChannelCommon.h \
|
||||
AudioChannelAgent.h
|
||||
|
||||
CPPSRCS += \
|
||||
AudioChannelService.cpp \
|
||||
AudioChannelServiceChild.cpp \
|
||||
AudioChannelAgent.cpp \
|
||||
$(NULL)
|
||||
|
||||
XPIDLSRCS = \
|
||||
nsIAudioChannelAgent.idl \
|
||||
$(NULL)
|
||||
|
||||
include $(topsrcdir)/config/config.mk
|
||||
|
98
dom/audiochannel/nsIAudioChannelAgent.idl
Normal file
98
dom/audiochannel/nsIAudioChannelAgent.idl
Normal file
@ -0,0 +1,98 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(35dddb4c-3d35-11e2-978c-10bf48d64bd4)]
|
||||
interface nsIAudioChannelAgentCallback : nsISupports
|
||||
{
|
||||
/**
|
||||
* Notified when the playable status of channel is changed.
|
||||
*
|
||||
* @param canPlay
|
||||
* Callback from agent to notify component of the playable status
|
||||
* of the channel. If canPlay is false, component SHOULD stop playing
|
||||
* media associated with this channel as soon as possible.
|
||||
*/
|
||||
void canPlayChanged(in boolean canPlay);
|
||||
};
|
||||
|
||||
/**
|
||||
* This interface provides an agent for gecko components to participate
|
||||
* in the audio channel service. Gecko components are responsible for
|
||||
* 1. Indicating what channel type they are using (via the init() member function).
|
||||
* 2. Before playing, checking the playable status of the channel.
|
||||
* 3. Notifying the agent when they start/stop using this channel.
|
||||
* 4. Notifying the agent of changes to the visibility of the component using
|
||||
* this channel.
|
||||
*
|
||||
* The agent will invoke a callback to notify Gecko components of
|
||||
* 1. Changes to the playable status of this channel.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(4d01d4f0-3d16-11e2-a0db-10bf48d64bd4)]
|
||||
interface nsIAudioChannelAgent : nsISupports
|
||||
{
|
||||
const long AUDIO_AGENT_CHANNEL_NORMAL = 0;
|
||||
const long AUDIO_AGENT_CHANNEL_CONTENT = 1;
|
||||
const long AUDIO_AGENT_CHANNEL_NOTIFICATION = 2;
|
||||
const long AUDIO_AGENT_CHANNEL_ALARM = 3;
|
||||
const long AUDIO_AGENT_CHANNEL_TELEPHONY = 4;
|
||||
const long AUDIO_AGENT_CHANNEL_RINGER = 5;
|
||||
const long AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION = 6;
|
||||
|
||||
const long AUDIO_AGENT_CHANNEL_ERROR = 1000;
|
||||
|
||||
/**
|
||||
* Before init() is called, this returns AUDIO_AGENT_CHANNEL_ERROR.
|
||||
*/
|
||||
readonly attribute long audioChannelType;
|
||||
|
||||
/**
|
||||
* Initialize the agent with a channel type.
|
||||
* Note: This function should only be called once.
|
||||
*
|
||||
* @param channelType
|
||||
* Audio Channel Type listed as above
|
||||
* @param callback
|
||||
* 1. Once the playable status changes, agent uses this callback function to notify
|
||||
* Gecko component.
|
||||
* 2. The callback is allowed to be null. Ex: telephony doesn't need to listen change
|
||||
* of the playable status.
|
||||
*/
|
||||
void init(in long channelType, in nsIAudioChannelAgentCallback callback);
|
||||
|
||||
/**
|
||||
* Notify the agent that we want to start playing.
|
||||
* Note: Gecko component SHOULD call this function first then start to
|
||||
* play audio stream only when return value is true.
|
||||
*
|
||||
*
|
||||
* @return
|
||||
* true: the agent has registered with audio channel service and the
|
||||
* component should start playback.
|
||||
* false: the agent has registered with audio channel service but the
|
||||
* component should not start playback.
|
||||
*/
|
||||
boolean startPlaying();
|
||||
|
||||
/**
|
||||
* Notify the agent we no longer want to play.
|
||||
*
|
||||
* Note : even if startPlaying() returned false, the agent would still be
|
||||
* registered with the audio channel service and receive callbacks for status changes.
|
||||
* So stopPlaying must still eventually be called to unregister the agent with the
|
||||
* channel service.
|
||||
*/
|
||||
void stopPlaying();
|
||||
|
||||
/**
|
||||
* Notify the agent of the visibility state of the window using this agent.
|
||||
* @param visible
|
||||
* True if the window associated with the agent is visible.
|
||||
*/
|
||||
void setVisibilityState(in boolean visible);
|
||||
|
||||
};
|
||||
|
@ -1847,8 +1847,6 @@ jsid nsDOMClassInfo::sSelf_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sOpener_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sAll_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sTags_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sBaseURIObject_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sNodePrincipal_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sDocumentURIObject_id=JSID_VOID;
|
||||
jsid nsDOMClassInfo::sWrappedJSObject_id = JSID_VOID;
|
||||
jsid nsDOMClassInfo::sURL_id = JSID_VOID;
|
||||
@ -2119,8 +2117,6 @@ nsDOMClassInfo::DefineStaticJSVals(JSContext *cx)
|
||||
SET_JSID_TO_STRING(sOpener_id, cx, "opener");
|
||||
SET_JSID_TO_STRING(sAll_id, cx, "all");
|
||||
SET_JSID_TO_STRING(sTags_id, cx, "tags");
|
||||
SET_JSID_TO_STRING(sBaseURIObject_id, cx, "baseURIObject");
|
||||
SET_JSID_TO_STRING(sNodePrincipal_id, cx, "nodePrincipal");
|
||||
SET_JSID_TO_STRING(sDocumentURIObject_id,cx,"documentURIObject");
|
||||
SET_JSID_TO_STRING(sWrappedJSObject_id, cx, "wrappedJSObject");
|
||||
SET_JSID_TO_STRING(sURL_id, cx, "URL");
|
||||
@ -5209,8 +5205,6 @@ nsDOMClassInfo::ShutDown()
|
||||
sOpener_id = JSID_VOID;
|
||||
sAll_id = JSID_VOID;
|
||||
sTags_id = JSID_VOID;
|
||||
sBaseURIObject_id = JSID_VOID;
|
||||
sNodePrincipal_id = JSID_VOID;
|
||||
sDocumentURIObject_id=JSID_VOID;
|
||||
sWrappedJSObject_id = JSID_VOID;
|
||||
sOnload_id = JSID_VOID;
|
||||
@ -7739,64 +7733,6 @@ GetterShim(JSContext *cx, JSHandleObject obj, JSHandleId /* unused */, JSMutable
|
||||
return JS_TRUE;
|
||||
}
|
||||
|
||||
// Can't be static so GetterShim will compile
|
||||
nsresult
|
||||
BaseURIObjectGetter(JSContext *cx, JSObject *obj, jsval *vp)
|
||||
{
|
||||
// This function duplicates some of the logic in XPC_WN_HelperGetProperty
|
||||
XPCWrappedNative *wrapper =
|
||||
XPCWrappedNative::GetWrappedNativeOfJSObject(cx, obj);
|
||||
|
||||
// The error checks duplicate code in THROW_AND_RETURN_IF_BAD_WRAPPER
|
||||
NS_ENSURE_TRUE(!wrapper || wrapper->IsValid(), NS_ERROR_XPC_HAS_BEEN_SHUTDOWN);
|
||||
|
||||
nsCOMPtr<nsINode> node = do_QueryWrappedNative(wrapper, obj);
|
||||
NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
|
||||
|
||||
nsCOMPtr<nsIURI> uri = node->GetBaseURI();
|
||||
return WrapNative(cx, JS_GetGlobalForScopeChain(cx), uri,
|
||||
&NS_GET_IID(nsIURI), true, vp);
|
||||
}
|
||||
|
||||
// Can't be static so GetterShim will compile
|
||||
nsresult
|
||||
NodePrincipalGetter(JSContext *cx, JSObject *obj, jsval *vp)
|
||||
{
|
||||
// This function duplicates some of the logic in XPC_WN_HelperGetProperty
|
||||
XPCWrappedNative *wrapper =
|
||||
XPCWrappedNative::GetWrappedNativeOfJSObject(cx, obj);
|
||||
|
||||
// The error checks duplicate code in THROW_AND_RETURN_IF_BAD_WRAPPER
|
||||
NS_ENSURE_TRUE(!wrapper || wrapper->IsValid(), NS_ERROR_XPC_HAS_BEEN_SHUTDOWN);
|
||||
|
||||
nsCOMPtr<nsINode> node = do_QueryWrappedNative(wrapper, obj);
|
||||
NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED);
|
||||
|
||||
return WrapNative(cx, JS_GetGlobalForScopeChain(cx), node->NodePrincipal(),
|
||||
&NS_GET_IID(nsIPrincipal), true, vp);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNodeSH::PostCreatePrototype(JSContext * cx, JSObject * proto)
|
||||
{
|
||||
// set up our proto first
|
||||
nsresult rv = nsDOMGenericSH::PostCreatePrototype(cx, proto);
|
||||
|
||||
if (xpc::AccessCheck::isChrome(js::GetObjectCompartment(proto))) {
|
||||
// Stick nodePrincipal and baseURIObject properties on there
|
||||
JS_DefinePropertyById(cx, proto, sNodePrincipal_id,
|
||||
JSVAL_VOID, GetterShim<NodePrincipalGetter>,
|
||||
nullptr,
|
||||
JSPROP_READONLY | JSPROP_SHARED);
|
||||
JS_DefinePropertyById(cx, proto, sBaseURIObject_id,
|
||||
JSVAL_VOID, GetterShim<BaseURIObjectGetter>,
|
||||
nullptr,
|
||||
JSPROP_READONLY | JSPROP_SHARED);
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNodeSH::PreCreate(nsISupports *nativeObj, JSContext *cx, JSObject *globalObj,
|
||||
JSObject **parentObj)
|
||||
@ -8562,6 +8498,7 @@ DocumentURIObjectGetter(JSContext *cx, JSObject *obj, jsval *vp)
|
||||
NS_IMETHODIMP
|
||||
nsDocumentSH::PostCreatePrototype(JSContext * cx, JSObject * proto)
|
||||
{
|
||||
// XXXbz when this goes away, kill GetterShim as well.
|
||||
// set up our proto first
|
||||
nsresult rv = nsNodeSH::PostCreatePrototype(cx, proto);
|
||||
|
||||
|
@ -257,8 +257,6 @@ public:
|
||||
static jsid sOpener_id;
|
||||
static jsid sAll_id;
|
||||
static jsid sTags_id;
|
||||
static jsid sBaseURIObject_id;
|
||||
static jsid sNodePrincipal_id;
|
||||
static jsid sDocumentURIObject_id;
|
||||
static jsid sJava_id;
|
||||
static jsid sPackages_id;
|
||||
@ -525,7 +523,6 @@ protected:
|
||||
public:
|
||||
NS_IMETHOD PreCreate(nsISupports *nativeObj, JSContext *cx,
|
||||
JSObject *globalObj, JSObject **parentObj);
|
||||
NS_IMETHOD PostCreatePrototype(JSContext * cx, JSObject * proto);
|
||||
NS_IMETHOD AddProperty(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
|
||||
JSObject *obj, jsid id, jsval *vp, bool *_retval);
|
||||
NS_IMETHOD NewResolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
|
||||
|
@ -51,6 +51,9 @@ enum nsDOMClassInfoID {
|
||||
DOMCI_CASTABLE_INTERFACE(nsINode, nsINode, 0, _extra) \
|
||||
DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::Element, mozilla::dom::Element,\
|
||||
1, _extra) \
|
||||
/* If this is ever removed, the IID for EventTarget can go away */ \
|
||||
DOMCI_CASTABLE_NODECL_INTERFACE(mozilla::dom::EventTarget, \
|
||||
mozilla::dom::EventTarget, 2, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsDocument, nsIDocument, 5, _extra) \
|
||||
DOMCI_CASTABLE_INTERFACE(nsGenericHTMLElement, nsGenericHTMLElement, 6, \
|
||||
_extra) \
|
||||
@ -68,6 +71,7 @@ DOMCI_CASTABLE_INTERFACES(unused)
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
class Element;
|
||||
class EventTarget;
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
|
@ -11220,27 +11220,16 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType)
|
||||
#define EVENT(name_, id_, type_, struct_) \
|
||||
NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx, \
|
||||
jsval *vp) { \
|
||||
nsEventListenerManager *elm = GetListenerManager(false); \
|
||||
if (elm) { \
|
||||
EventHandlerNonNull* h = elm->GetEventHandler(nsGkAtoms::on##name_); \
|
||||
if (h) { \
|
||||
*vp = JS::ObjectValue(*h->Callable()); \
|
||||
return NS_OK; \
|
||||
} \
|
||||
} \
|
||||
*vp = JSVAL_NULL; \
|
||||
EventHandlerNonNull* h = GetOn##name_(); \
|
||||
vp->setObjectOrNull(h ? h->Callable() : nullptr); \
|
||||
return NS_OK; \
|
||||
} \
|
||||
NS_IMETHODIMP nsGlobalWindow::SetOn##name_(JSContext *cx, \
|
||||
const jsval &v) { \
|
||||
nsEventListenerManager *elm = GetListenerManager(true); \
|
||||
if (!elm) { \
|
||||
return NS_ERROR_OUT_OF_MEMORY; \
|
||||
} \
|
||||
\
|
||||
JSObject *obj = mJSObject; \
|
||||
if (!obj) { \
|
||||
return NS_ERROR_UNEXPECTED; \
|
||||
/* Just silently do nothing */ \
|
||||
return NS_OK; \
|
||||
} \
|
||||
nsRefPtr<EventHandlerNonNull> handler; \
|
||||
JSObject *callable; \
|
||||
@ -11252,7 +11241,9 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType)
|
||||
return NS_ERROR_OUT_OF_MEMORY; \
|
||||
} \
|
||||
} \
|
||||
return elm->SetEventHandler(nsGkAtoms::on##name_, handler); \
|
||||
ErrorResult rv; \
|
||||
SetOn##name_(handler, rv); \
|
||||
return rv.ErrorCode(); \
|
||||
}
|
||||
#define ERROR_EVENT(name_, id_, type_, struct_) \
|
||||
NS_IMETHODIMP nsGlobalWindow::GetOn##name_(JSContext *cx, \
|
||||
@ -11334,5 +11325,7 @@ nsGlobalWindow::DisableNetworkEvent(uint32_t aType)
|
||||
#include "nsEventNameList.h"
|
||||
#undef TOUCH_EVENT
|
||||
#undef WINDOW_ONLY_EVENT
|
||||
#undef BEFOREUNLOAD_EVENT
|
||||
#undef ERROR_EVENT
|
||||
#undef EVENT
|
||||
|
||||
|
@ -619,6 +619,64 @@ public:
|
||||
{
|
||||
mAllowScriptsToClose = true;
|
||||
}
|
||||
|
||||
#define EVENT(name_, id_, type_, struct_) \
|
||||
mozilla::dom::EventHandlerNonNull* GetOn##name_() \
|
||||
{ \
|
||||
nsEventListenerManager *elm = GetListenerManager(false); \
|
||||
return elm ? elm->GetEventHandler(nsGkAtoms::on##name_) : nullptr; \
|
||||
} \
|
||||
void SetOn##name_(mozilla::dom::EventHandlerNonNull* handler, \
|
||||
mozilla::ErrorResult& error) \
|
||||
{ \
|
||||
nsEventListenerManager *elm = GetListenerManager(true); \
|
||||
if (elm) { \
|
||||
error = elm->SetEventHandler(nsGkAtoms::on##name_, handler); \
|
||||
} else { \
|
||||
error.Throw(NS_ERROR_OUT_OF_MEMORY); \
|
||||
} \
|
||||
}
|
||||
#define ERROR_EVENT(name_, id_, type_, struct_) \
|
||||
mozilla::dom::OnErrorEventHandlerNonNull* GetOn##name_() \
|
||||
{ \
|
||||
nsEventListenerManager *elm = GetListenerManager(false); \
|
||||
return elm ? elm->GetOnErrorEventHandler() : nullptr; \
|
||||
} \
|
||||
void SetOn##name_(mozilla::dom::OnErrorEventHandlerNonNull* handler, \
|
||||
mozilla::ErrorResult& error) \
|
||||
{ \
|
||||
nsEventListenerManager *elm = GetListenerManager(true); \
|
||||
if (elm) { \
|
||||
error = elm->SetEventHandler(handler); \
|
||||
} else { \
|
||||
error.Throw(NS_ERROR_OUT_OF_MEMORY); \
|
||||
} \
|
||||
}
|
||||
#define BEFOREUNLOAD_EVENT(name_, id_, type_, struct_) \
|
||||
mozilla::dom::BeforeUnloadEventHandlerNonNull* GetOn##name_() \
|
||||
{ \
|
||||
nsEventListenerManager *elm = GetListenerManager(false); \
|
||||
return elm ? elm->GetOnBeforeUnloadEventHandler() : nullptr; \
|
||||
} \
|
||||
void SetOn##name_(mozilla::dom::BeforeUnloadEventHandlerNonNull* handler, \
|
||||
mozilla::ErrorResult& error) \
|
||||
{ \
|
||||
nsEventListenerManager *elm = GetListenerManager(true); \
|
||||
if (elm) { \
|
||||
error = elm->SetEventHandler(handler); \
|
||||
} else { \
|
||||
error.Throw(NS_ERROR_OUT_OF_MEMORY); \
|
||||
} \
|
||||
}
|
||||
#define WINDOW_ONLY_EVENT EVENT
|
||||
#define TOUCH_EVENT EVENT
|
||||
#include "nsEventNameList.h"
|
||||
#undef TOUCH_EVENT
|
||||
#undef WINDOW_ONLY_EVENT
|
||||
#undef BEFOREUNLOAD_EVENT
|
||||
#undef ERROR_EVENT
|
||||
#undef EVENT
|
||||
|
||||
protected:
|
||||
// Array of idle observers that are notified of idle events.
|
||||
nsTObserverArray<IdleObserverHolder> mIdleObservers;
|
||||
|
@ -221,12 +221,18 @@ CreateInterfaceObject(JSContext* cx, JSObject* global,
|
||||
const char* name)
|
||||
{
|
||||
JSObject* constructor;
|
||||
bool isCallbackInterface = constructorClass == js::Jsvalify(&js::ObjectClass);
|
||||
if (constructorClass) {
|
||||
JSObject* functionProto = JS_GetFunctionPrototype(cx, global);
|
||||
if (!functionProto) {
|
||||
JSObject* constructorProto;
|
||||
if (isCallbackInterface) {
|
||||
constructorProto = JS_GetObjectPrototype(cx, global);
|
||||
} else {
|
||||
constructorProto = JS_GetFunctionPrototype(cx, global);
|
||||
}
|
||||
if (!constructorProto) {
|
||||
return NULL;
|
||||
}
|
||||
constructor = JS_NewObject(cx, constructorClass, functionProto, global);
|
||||
constructor = JS_NewObject(cx, constructorClass, constructorProto, global);
|
||||
} else {
|
||||
MOZ_ASSERT(constructorNative);
|
||||
JSFunction* fun = js::NewFunctionWithReserved(cx, Constructor, ctorNargs,
|
||||
@ -244,7 +250,9 @@ CreateInterfaceObject(JSContext* cx, JSObject* global,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (constructorClass) {
|
||||
if (constructorClass && !isCallbackInterface) {
|
||||
// Have to shadow Function.prototype.toString, since that throws
|
||||
// on things that are not js::FunctionClass.
|
||||
JSFunction* toString = js::DefineFunctionWithReserved(cx, constructor,
|
||||
"toString",
|
||||
InterfaceObjectToString,
|
||||
@ -570,8 +578,8 @@ ThrowingConstructor(JSContext* cx, unsigned argc, JS::Value* vp)
|
||||
inline const NativePropertyHooks*
|
||||
GetNativePropertyHooks(JSContext *cx, JSObject *obj, DOMObjectType& type)
|
||||
{
|
||||
const DOMClass* domClass;
|
||||
if (GetDOMClass(obj, domClass) != eNonDOMObject) {
|
||||
const DOMClass* domClass = GetDOMClass(obj);
|
||||
if (domClass) {
|
||||
type = eInstance;
|
||||
return domClass->mNativeHooks;
|
||||
}
|
||||
|
@ -91,32 +91,17 @@ IsDOMIfaceAndProtoClass(const js::Class* clasp)
|
||||
return IsDOMIfaceAndProtoClass(Jsvalify(clasp));
|
||||
}
|
||||
|
||||
// It's ok for eRegularDOMObject and eProxyDOMObject to be the same, but
|
||||
// eNonDOMObject should always be different from the other two. This enum
|
||||
// shouldn't be used to differentiate between non-proxy and proxy bindings.
|
||||
enum DOMObjectSlot {
|
||||
eNonDOMObject = -1,
|
||||
eRegularDOMObject = DOM_OBJECT_SLOT,
|
||||
eProxyDOMObject = DOM_PROXY_OBJECT_SLOT
|
||||
};
|
||||
|
||||
MOZ_STATIC_ASSERT(DOM_OBJECT_SLOT == js::JSSLOT_PROXY_PRIVATE,
|
||||
"JSSLOT_PROXY_PRIVATE doesn't match DOM_OBJECT_SLOT. "
|
||||
"Expect bad things");
|
||||
template <class T>
|
||||
inline T*
|
||||
UnwrapDOMObject(JSObject* obj, DOMObjectSlot slot)
|
||||
UnwrapDOMObject(JSObject* obj)
|
||||
{
|
||||
MOZ_ASSERT(slot != eNonDOMObject,
|
||||
MOZ_ASSERT(IsDOMClass(js::GetObjectClass(obj)) || IsDOMProxy(obj),
|
||||
"Don't pass non-DOM objects to this function");
|
||||
|
||||
#ifdef DEBUG
|
||||
if (IsDOMClass(js::GetObjectClass(obj))) {
|
||||
MOZ_ASSERT(slot == eRegularDOMObject);
|
||||
} else {
|
||||
MOZ_ASSERT(IsDOMProxy(obj));
|
||||
MOZ_ASSERT(slot == eProxyDOMObject);
|
||||
}
|
||||
#endif
|
||||
|
||||
JS::Value val = js::GetReservedSlot(obj, slot);
|
||||
JS::Value val = js::GetReservedSlot(obj, DOM_OBJECT_SLOT);
|
||||
// XXXbz/khuey worker code tries to unwrap interface objects (which have
|
||||
// nothing here). That needs to stop.
|
||||
// XXX We don't null-check UnwrapObject's result; aren't we going to crash
|
||||
@ -128,7 +113,6 @@ UnwrapDOMObject(JSObject* obj, DOMObjectSlot slot)
|
||||
return static_cast<T*>(val.toPrivate());
|
||||
}
|
||||
|
||||
// Only use this with a new DOM binding object (either proxy or regular).
|
||||
inline const DOMClass*
|
||||
GetDOMClass(JSObject* obj)
|
||||
{
|
||||
@ -137,41 +121,25 @@ GetDOMClass(JSObject* obj)
|
||||
return &DOMJSClass::FromJSClass(clasp)->mClass;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(IsDOMProxy(obj));
|
||||
js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
|
||||
return &static_cast<DOMProxyHandler*>(handler)->mClass;
|
||||
}
|
||||
|
||||
inline DOMObjectSlot
|
||||
GetDOMClass(JSObject* obj, const DOMClass*& result)
|
||||
{
|
||||
js::Class* clasp = js::GetObjectClass(obj);
|
||||
if (IsDOMClass(clasp)) {
|
||||
result = &DOMJSClass::FromJSClass(clasp)->mClass;
|
||||
return eRegularDOMObject;
|
||||
}
|
||||
|
||||
if (js::IsObjectProxyClass(clasp) || js::IsFunctionProxyClass(clasp)) {
|
||||
js::BaseProxyHandler* handler = js::GetProxyHandler(obj);
|
||||
if (handler->family() == ProxyFamily()) {
|
||||
result = &static_cast<DOMProxyHandler*>(handler)->mClass;
|
||||
return eProxyDOMObject;
|
||||
return &static_cast<DOMProxyHandler*>(handler)->mClass;
|
||||
}
|
||||
}
|
||||
|
||||
return eNonDOMObject;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline bool
|
||||
UnwrapDOMObjectToISupports(JSObject* obj, nsISupports*& result)
|
||||
{
|
||||
const DOMClass* clasp;
|
||||
DOMObjectSlot slot = GetDOMClass(obj, clasp);
|
||||
if (slot == eNonDOMObject || !clasp->mDOMObjectIsISupports) {
|
||||
const DOMClass* clasp = GetDOMClass(obj);
|
||||
if (!clasp || !clasp->mDOMObjectIsISupports) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = UnwrapDOMObject<nsISupports>(obj, slot);
|
||||
result = UnwrapDOMObject<nsISupports>(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -191,9 +159,8 @@ MOZ_ALWAYS_INLINE nsresult
|
||||
UnwrapObject(JSContext* cx, JSObject* obj, U& value)
|
||||
{
|
||||
/* First check to see whether we have a DOM object */
|
||||
const DOMClass* domClass;
|
||||
DOMObjectSlot slot = GetDOMClass(obj, domClass);
|
||||
if (slot == eNonDOMObject) {
|
||||
const DOMClass* domClass = GetDOMClass(obj);
|
||||
if (!domClass) {
|
||||
/* Maybe we have a security wrapper or outer window? */
|
||||
if (!js::IsWrapper(obj)) {
|
||||
/* Not a DOM object, not a wrapper, just bail */
|
||||
@ -205,8 +172,8 @@ UnwrapObject(JSContext* cx, JSObject* obj, U& value)
|
||||
return NS_ERROR_XPC_SECURITY_MANAGER_VETO;
|
||||
}
|
||||
MOZ_ASSERT(!js::IsWrapper(obj));
|
||||
slot = GetDOMClass(obj, domClass);
|
||||
if (slot == eNonDOMObject) {
|
||||
domClass = GetDOMClass(obj);
|
||||
if (!domClass) {
|
||||
/* We don't have a DOM object */
|
||||
return NS_ERROR_XPC_BAD_CONVERT_JS;
|
||||
}
|
||||
@ -217,7 +184,7 @@ UnwrapObject(JSContext* cx, JSObject* obj, U& value)
|
||||
class identified by protoID. */
|
||||
if (domClass->mInterfaceChain[PrototypeTraits<PrototypeID>::Depth] ==
|
||||
PrototypeID) {
|
||||
value = UnwrapDOMObject<T>(obj, slot);
|
||||
value = UnwrapDOMObject<T>(obj);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@ -543,11 +510,10 @@ WrapNewBindingObject(JSContext* cx, JSObject* scope, T* value, JS::Value* vp)
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
const DOMClass* clasp = nullptr;
|
||||
DOMObjectSlot slot = GetDOMClass(obj, clasp);
|
||||
// slot can be eNonDOMObject if the cache contained a non-DOM object from a
|
||||
const DOMClass* clasp = GetDOMClass(obj);
|
||||
// clasp can be null if the cache contained a non-DOM object from a
|
||||
// different compartment than scope.
|
||||
if (slot != eNonDOMObject) {
|
||||
if (clasp) {
|
||||
// Some sanity asserts about our object. Specifically:
|
||||
// 1) If our class claims we're nsISupports, we better be nsISupports
|
||||
// XXXbz ideally, we could assert that reinterpret_cast to nsISupports
|
||||
|
@ -221,10 +221,9 @@ DOMInterfaces = {
|
||||
|
||||
'EventTarget': [
|
||||
{
|
||||
'nativeType': 'nsDOMEventTargetHelper',
|
||||
'hasInstanceInterface': 'nsIDOMEventTarget',
|
||||
'hasXPConnectImpls': True,
|
||||
'concrete': False,
|
||||
'prefable': True,
|
||||
},
|
||||
{
|
||||
'workers': True,
|
||||
@ -260,6 +259,17 @@ DOMInterfaces = {
|
||||
'resultNotAddRefed': [ 'item' ]
|
||||
},
|
||||
|
||||
'HTMLElement': {
|
||||
'nativeType': 'nsGenericHTMLElement',
|
||||
'register': False,
|
||||
'hasXPConnectImpls': True,
|
||||
'hasInstanceInterface': 'nsIDOMHTMLElement',
|
||||
'resultNotAddRefed': [
|
||||
'itemType', 'itemRef', 'itemProp', 'properties', 'contextMenu', 'style',
|
||||
'offsetParent'
|
||||
]
|
||||
},
|
||||
|
||||
'HTMLOptionsCollection': {
|
||||
'nativeType': 'nsHTMLOptionCollection',
|
||||
'headerFile': 'nsHTMLSelectElement.h',
|
||||
@ -274,6 +284,12 @@ DOMInterfaces = {
|
||||
'resultNotAddRefed': [ 'item', 'namedItem', 'names' ]
|
||||
},
|
||||
|
||||
'HTMLUnknownElement': {
|
||||
'nativeType': 'nsHTMLUnknownElement',
|
||||
'register': False,
|
||||
'hasXPConnectImpls': True
|
||||
},
|
||||
|
||||
'IID': [
|
||||
{
|
||||
'nativeType': 'nsIJSID',
|
||||
@ -728,6 +744,7 @@ def addExternalHTMLElement(element):
|
||||
|
||||
addExternalHTMLElement('HTMLCanvasElement')
|
||||
addExternalHTMLElement('HTMLImageElement')
|
||||
addExternalHTMLElement('HTMLMenuElement')
|
||||
addExternalHTMLElement('HTMLOptionElement')
|
||||
addExternalHTMLElement('HTMLOptGroupElement')
|
||||
addExternalHTMLElement('HTMLVideoElement')
|
||||
@ -743,7 +760,6 @@ addExternalIface('DOMStringList', nativeType='nsDOMStringList',
|
||||
headerFile='nsDOMLists.h')
|
||||
addExternalIface('File')
|
||||
addExternalIface('HitRegionOptions', nativeType='nsISupports')
|
||||
addExternalIface('HTMLElement')
|
||||
addExternalIface('LockedFile')
|
||||
addExternalIface('MediaStream')
|
||||
addExternalIface('NamedNodeMap')
|
||||
|
@ -88,6 +88,16 @@ public:
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit CallbackFunction(CallbackFunction* aCallbackFunction)
|
||||
: mCallable(aCallbackFunction->mCallable)
|
||||
{
|
||||
// Set mCallable before we hold, on the off chance that a GC could somehow
|
||||
// happen in there... (which would be pretty odd, granted).
|
||||
// Make sure we'll be able to drop as needed
|
||||
nsLayoutStatics::AddRef();
|
||||
NS_HOLD_JS_OBJECTS(this, CallbackFunction);
|
||||
}
|
||||
|
||||
void DropCallback()
|
||||
{
|
||||
if (mCallable) {
|
||||
|
@ -607,40 +607,45 @@ def UnionTypes(descriptors, dictionaries, callbacks, config):
|
||||
return (headers, implheaders, declarations,
|
||||
CGList(SortedDictValues(unionStructs), "\n"))
|
||||
|
||||
def UnionConversions(descriptors):
|
||||
def UnionConversions(descriptors, dictionaries, callbacks, config):
|
||||
"""
|
||||
Returns a CGThing to declare all union argument conversion helper structs.
|
||||
"""
|
||||
# Now find all the things we'll need as arguments because we
|
||||
# need to unwrap them.
|
||||
headers = set()
|
||||
unionConversions = dict()
|
||||
for d in descriptors:
|
||||
if d.interface.isExternal():
|
||||
continue
|
||||
|
||||
def addUnionTypes(type):
|
||||
if type.isUnion():
|
||||
type = type.unroll()
|
||||
name = str(type)
|
||||
if not name in unionConversions:
|
||||
unionConversions[name] = CGUnionConversionStruct(type, d)
|
||||
def addInfoForType(t, descriptor=None, dictionary=None):
|
||||
"""
|
||||
Add info for the given type. descriptor and dictionary, if passed, are
|
||||
used to figure out what to do with interface types.
|
||||
"""
|
||||
assert not descriptor or not dictionary
|
||||
t = t.unroll()
|
||||
if not t.isUnion():
|
||||
return
|
||||
name = str(t)
|
||||
if not name in unionConversions:
|
||||
providers = getRelevantProviders(descriptor, dictionary,
|
||||
config)
|
||||
unionConversions[name] = CGUnionConversionStruct(t, providers[0])
|
||||
for f in t.flatMemberTypes:
|
||||
f = f.unroll()
|
||||
if f.isInterface():
|
||||
if f.isSpiderMonkeyInterface():
|
||||
headers.add("jsfriendapi.h")
|
||||
headers.add("mozilla/dom/TypedArray.h")
|
||||
elif not f.inner.isExternal():
|
||||
headers.add(CGHeaders.getDeclarationFilename(f.inner))
|
||||
elif f.isDictionary():
|
||||
headers.add(CGHeaders.getDeclarationFilename(f.inner))
|
||||
|
||||
members = [m for m in d.interface.members]
|
||||
if d.interface.ctor():
|
||||
members.append(d.interface.ctor())
|
||||
signatures = [s for m in members if m.isMethod() for s in m.signatures()]
|
||||
for s in signatures:
|
||||
assert len(s) == 2
|
||||
(_, arguments) = s
|
||||
for a in arguments:
|
||||
addUnionTypes(a.type)
|
||||
callForEachType(descriptors, dictionaries, callbacks, addInfoForType)
|
||||
|
||||
for m in members:
|
||||
if m.isAttr() and not m.readonly:
|
||||
addUnionTypes(m.type)
|
||||
|
||||
return CGWrapper(CGList(SortedDictValues(unionConversions), "\n"),
|
||||
post="\n\n")
|
||||
return (headers,
|
||||
CGWrapper(CGList(SortedDictValues(unionConversions), "\n"),
|
||||
post="\n\n"))
|
||||
|
||||
class Argument():
|
||||
"""
|
||||
@ -744,7 +749,7 @@ class CGAbstractClassHook(CGAbstractStaticMethod):
|
||||
|
||||
def definition_body_prologue(self):
|
||||
return """
|
||||
%s* self = UnwrapDOMObject<%s>(obj, eRegularDOMObject);
|
||||
%s* self = UnwrapDOMObject<%s>(obj);
|
||||
""" % (self.descriptor.nativeType, self.descriptor.nativeType)
|
||||
|
||||
def definition_body(self):
|
||||
@ -1458,9 +1463,11 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
||||
" return;\n" +
|
||||
"}\n") % getParentProto
|
||||
|
||||
needConstructor = (needInterfaceObject and
|
||||
not self.descriptor.hasInstanceInterface)
|
||||
constructHook = "&" + CONSTRUCT_HOOK_NAME + "_holder"
|
||||
if (needInterfaceObject and
|
||||
self.descriptor.needsConstructHookHolder()):
|
||||
constructHookHolder = "&" + CONSTRUCT_HOOK_NAME + "_holder"
|
||||
else:
|
||||
constructHookHolder = "nullptr"
|
||||
if self.descriptor.interface.ctor():
|
||||
constructArgs = methodLength(self.descriptor.interface.ctor())
|
||||
else:
|
||||
@ -1475,6 +1482,8 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
||||
if needInterfaceObject:
|
||||
if self.descriptor.hasInstanceInterface:
|
||||
interfaceClass = "&InterfaceObjectClass.mBase"
|
||||
elif self.descriptor.interface.isCallback():
|
||||
interfaceClass = "js::Jsvalify(&js::ObjectClass)"
|
||||
else:
|
||||
interfaceClass = "nullptr"
|
||||
interfaceCache = "&protoAndIfaceArray[constructors::id::%s]" % self.descriptor.name
|
||||
@ -1507,7 +1516,7 @@ class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
|
||||
" %s,\n"
|
||||
" %s);" % (
|
||||
protoClass, protoCache,
|
||||
interfaceClass, constructHook if needConstructor else "nullptr",
|
||||
interfaceClass, constructHookHolder,
|
||||
constructArgs, interfaceCache,
|
||||
domClass,
|
||||
properties,
|
||||
@ -4997,22 +5006,28 @@ class ClassConstructor(ClassItem):
|
||||
visibility determines the visibility of the constructor (public,
|
||||
protected, private), defaults to private.
|
||||
|
||||
explicit should be True if the constructor should be marked explicit.
|
||||
|
||||
baseConstructors is a list of strings containing calls to base constructors,
|
||||
defaults to None.
|
||||
|
||||
body contains a string with the code for the constructor, defaults to None.
|
||||
"""
|
||||
def __init__(self, args, inline=False, bodyInHeader=False,
|
||||
visibility="private", baseConstructors=None, body=None):
|
||||
visibility="private", explicit=False, baseConstructors=None,
|
||||
body=None):
|
||||
self.args = args
|
||||
self.inline = inline or bodyInHeader
|
||||
self.bodyInHeader = bodyInHeader
|
||||
self.explicit = explicit
|
||||
self.baseConstructors = baseConstructors
|
||||
self.body = body
|
||||
ClassItem.__init__(self, None, visibility)
|
||||
|
||||
def getDecorators(self, declaring):
|
||||
decorators = []
|
||||
if self.explicit:
|
||||
decorators.append('explicit')
|
||||
if self.inline and declaring:
|
||||
decorators.append('inline')
|
||||
if decorators:
|
||||
@ -6148,7 +6163,8 @@ class CGDescriptor(CGThing):
|
||||
cgThings.append(CGClassConstructHook(descriptor))
|
||||
cgThings.append(CGClassHasInstanceHook(descriptor))
|
||||
cgThings.append(CGInterfaceObjectJSClass(descriptor, properties))
|
||||
cgThings.append(CGClassConstructHookHolder(descriptor))
|
||||
if descriptor.needsConstructHookHolder():
|
||||
cgThings.append(CGClassConstructHookHolder(descriptor))
|
||||
|
||||
if descriptor.interface.hasInterfacePrototypeObject():
|
||||
cgThings.append(CGPrototypeJSClass(descriptor, properties))
|
||||
@ -7373,7 +7389,7 @@ class CGCallbackFunction(CGClass):
|
||||
callCallback = CallCallback(callback, descriptorProvider)
|
||||
CGClass.__init__(self, name,
|
||||
bases=[ClassBase("CallbackFunction")],
|
||||
constructors=[self.getConstructor()],
|
||||
constructors=self.getConstructors(),
|
||||
methods=self.getCallImpls(callCallback))
|
||||
self.generatable = True
|
||||
except NoSuchDescriptorError, err:
|
||||
@ -7391,8 +7407,8 @@ class CGCallbackFunction(CGClass):
|
||||
return ""
|
||||
return CGClass.declare(self)
|
||||
|
||||
def getConstructor(self):
|
||||
return ClassConstructor(
|
||||
def getConstructors(self):
|
||||
return [ClassConstructor(
|
||||
[Argument("JSContext*", "cx"),
|
||||
Argument("JSObject*", "aOwner"),
|
||||
Argument("JSObject*", "aCallable"),
|
||||
@ -7402,7 +7418,16 @@ class CGCallbackFunction(CGClass):
|
||||
baseConstructors=[
|
||||
"CallbackFunction(cx, aOwner, aCallable, aInited)"
|
||||
],
|
||||
body="")
|
||||
body=""),
|
||||
ClassConstructor(
|
||||
[Argument("CallbackFunction*", "aOther")],
|
||||
bodyInHeader=True,
|
||||
visibility="public",
|
||||
explicit=True,
|
||||
baseConstructors=[
|
||||
"CallbackFunction(aOther)"
|
||||
],
|
||||
body="")]
|
||||
|
||||
def getCallImpls(self, callCallback):
|
||||
args = list(callCallback.args)
|
||||
@ -7652,8 +7677,21 @@ class GlobalGenRoots():
|
||||
idEnum = CGNamespacedEnum('id', 'ID', ['_ID_Start'] + protos,
|
||||
[0, '_ID_Start'])
|
||||
idEnum = CGList([idEnum])
|
||||
idEnum.append(CGGeneric(declare="const unsigned MaxProtoChainLength = " +
|
||||
str(config.maxProtoChainLength) + ";\n\n"))
|
||||
|
||||
# This is only used by DOM worker code, once there are no more consumers
|
||||
# of INTERFACE_CHAIN_* this code should be removed.
|
||||
def ifaceChainMacro(ifaceCount):
|
||||
supplied = [CGGeneric(declare="_iface_" + str(i + 1)) for i in range(ifaceCount)]
|
||||
remaining = [CGGeneric(declare="prototypes::id::_ID_Count")] * (config.maxProtoChainLength - ifaceCount)
|
||||
macro = CGWrapper(CGList(supplied, ", "),
|
||||
pre="#define INTERFACE_CHAIN_" + str(ifaceCount) + "(",
|
||||
post=") \\\n")
|
||||
macroContent = CGIndenter(CGList(supplied + remaining, ", \\\n"))
|
||||
macroContent = CGIndenter(CGWrapper(macroContent, pre="{ \\\n",
|
||||
post=" \\\n}"))
|
||||
return CGWrapper(CGList([macro, macroContent]), post="\n\n")
|
||||
|
||||
idEnum.append(ifaceChainMacro(1))
|
||||
|
||||
# Wrap all of that in our namespaces.
|
||||
idEnum = CGNamespace.build(['mozilla', 'dom', 'prototypes'],
|
||||
@ -7776,14 +7814,18 @@ struct PrototypeIDMap;
|
||||
@staticmethod
|
||||
def UnionConversions(config):
|
||||
|
||||
unions = UnionConversions(config.getDescriptors())
|
||||
(headers, unions) = UnionConversions(config.getDescriptors(),
|
||||
config.getDictionaries(),
|
||||
config.getCallbacks(),
|
||||
config)
|
||||
|
||||
# Wrap all of that in our namespaces.
|
||||
curr = CGNamespace.build(['mozilla', 'dom'], unions)
|
||||
|
||||
curr = CGWrapper(curr, post='\n')
|
||||
|
||||
curr = CGHeaders([], [], [], ["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h", "XPCWrapper.h"], [], curr)
|
||||
headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h", "XPCWrapper.h"])
|
||||
curr = CGHeaders([], [], [], headers, [], curr)
|
||||
|
||||
# Add include guards.
|
||||
curr = CGIncludeGuard('UnionConversions', curr)
|
||||
|
@ -390,3 +390,7 @@ class Descriptor(DescriptorProvider):
|
||||
|
||||
def supportsNamedProperties(self):
|
||||
return self.operations['NamedGetter'] is not None
|
||||
|
||||
def needsConstructHookHolder(self):
|
||||
assert self.interface.hasInterfaceObject()
|
||||
return not self.hasInstanceInterface and not self.interface.isCallback()
|
||||
|
@ -67,7 +67,7 @@ DOMProxyHandler::EnsureExpandoObject(JSContext* cx, JSObject* obj)
|
||||
}
|
||||
|
||||
nsWrapperCache* cache;
|
||||
CallQueryInterface(UnwrapDOMObject<nsISupports>(obj, eProxyDOMObject), &cache);
|
||||
CallQueryInterface(UnwrapDOMObject<nsISupports>(obj), &cache);
|
||||
cache->SetPreservingWrapper(true);
|
||||
|
||||
js::SetProxyExtra(obj, JSPROXYSLOT_EXPANDO, ObjectValue(*expando));
|
||||
|
@ -251,16 +251,17 @@ ContactDB.prototype = {
|
||||
|
||||
// Chop off the first characters
|
||||
let number = aContact.properties[field][i].value;
|
||||
let search = {};
|
||||
if (number) {
|
||||
for (let i = 0; i < number.length; i++) {
|
||||
contact.search[field].push(number.substring(0, number.length - i));
|
||||
search[number.substring(i, number.length)] = 1;
|
||||
}
|
||||
// Store +1-234-567 as ["1234567", "234567"...]
|
||||
let digits = number.match(/\d/g);
|
||||
if (digits && number.length != digits.length) {
|
||||
digits = digits.join('');
|
||||
for(let i = 0; i < digits.length; i++) {
|
||||
contact.search[field].push(digits.substring(0, digits.length - i));
|
||||
search[digits.substring(i, digits.length)] = 1;
|
||||
}
|
||||
}
|
||||
if (DEBUG) debug("lookup: " + JSON.stringify(contact.search[field]));
|
||||
@ -271,12 +272,21 @@ ContactDB.prototype = {
|
||||
debug("NationalNumber: " + parsedNumber.nationalNumber);
|
||||
debug("NationalFormat: " + parsedNumber.nationalFormat);
|
||||
if (number.toString() !== parsedNumber.internationalNumber) {
|
||||
contact.search[field].push(parsedNumber.internationalNumber);
|
||||
let digits = parsedNumber.internationalNumber.match(/\d/g);
|
||||
if (digits) {
|
||||
digits = digits.join('');
|
||||
for(let i = 0; i < digits.length; i++) {
|
||||
search[digits.substring(i, digits.length)] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
dump("Warning: No international number found for " + number + "\n");
|
||||
}
|
||||
}
|
||||
for (let num in search) {
|
||||
contact.search[field].push(num);
|
||||
}
|
||||
} else if (field == "email") {
|
||||
let address = aContact.properties[field][i].value;
|
||||
if (address && typeof address == "string") {
|
||||
@ -439,6 +449,12 @@ ContactDB.prototype = {
|
||||
let tmp = typeof options.filterValue == "string"
|
||||
? options.filterValue.toLowerCase()
|
||||
: options.filterValue.toString().toLowerCase();
|
||||
if (key === 'tel') {
|
||||
let digits = tmp.match(/\d/g);
|
||||
if (digits) {
|
||||
tmp = digits.join('');
|
||||
}
|
||||
}
|
||||
let range = this._global.IDBKeyRange.bound(tmp, tmp + "\uFFFF");
|
||||
let index = store.index(key + "LowerCase");
|
||||
request = index.mozGetAll(range, limit);
|
||||
|
@ -11,4 +11,6 @@ include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
PARALLEL_DIRS = interfaces src
|
||||
|
||||
TEST_DIRS += tests
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
16
dom/icc/tests/Makefile.in
Normal file
16
dom/icc/tests/Makefile.in
Normal file
@ -0,0 +1,16 @@
|
||||
# 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/.
|
||||
|
||||
DEPTH = @DEPTH@
|
||||
topsrcdir = @top_srcdir@
|
||||
srcdir = @srcdir@
|
||||
VPATH = @srcdir@
|
||||
|
||||
relativesrcdir = @relativesrcdir@
|
||||
|
||||
include $(DEPTH)/config/autoconf.mk
|
||||
|
||||
MODULE = test_dom_icc
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
6
dom/icc/tests/marionette/manifest.ini
Normal file
6
dom/icc/tests/marionette/manifest.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
b2g = true
|
||||
browser = false
|
||||
qemu = true
|
||||
|
||||
[test_stk_proactive_command.js]
|
60
dom/icc/tests/marionette/test_stk_proactive_command.js
Normal file
60
dom/icc/tests/marionette/test_stk_proactive_command.js
Normal file
@ -0,0 +1,60 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
MARIONETTE_TIMEOUT = 30000;
|
||||
|
||||
SpecialPowers.addPermission("mobileconnection", true, document);
|
||||
|
||||
let icc = navigator.mozMobileConnection.icc;
|
||||
ok(icc instanceof MozIccManager, "icc is instanceof " + icc.constructor);
|
||||
|
||||
function testDisplayTextGsm7BitEncoding(cmd) {
|
||||
log("STK CMD " + JSON.stringify(cmd));
|
||||
is(cmd.typeOfCommand, icc.STK_CMD_DISPLAY_TEXT);
|
||||
is(cmd.options.userClear, true);
|
||||
is(cmd.options.text, "Saldo 2.04 E. Validez 20/05/13. ");
|
||||
|
||||
runNextTest();
|
||||
}
|
||||
|
||||
let tests = [
|
||||
{command: "d0288103012180820281020d1d00d3309bfc06c95c301aa8e80259c3ec34b9ac07c9602f58ed159bb940",
|
||||
func: testDisplayTextGsm7BitEncoding},
|
||||
];
|
||||
|
||||
let pendingEmulatorCmdCount = 0;
|
||||
function sendStkPduToEmulator(cmd, func) {
|
||||
++pendingEmulatorCmdCount;
|
||||
|
||||
runEmulatorCmd(cmd, function (result) {
|
||||
--pendingEmulatorCmdCount;
|
||||
is(result[0], "OK");
|
||||
});
|
||||
|
||||
icc.onstkcommand = function (evt) {
|
||||
func(evt.command);
|
||||
}
|
||||
}
|
||||
|
||||
function runNextTest() {
|
||||
let test = tests.pop();
|
||||
if (!test) {
|
||||
cleanUp();
|
||||
return;
|
||||
}
|
||||
|
||||
let cmd = "stk pdu " + test.command;
|
||||
sendStkPduToEmulator(cmd, test.func)
|
||||
}
|
||||
|
||||
function cleanUp() {
|
||||
if (pendingEmulatorCmdCount) {
|
||||
window.setTimeout(cleanUp, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
SpecialPowers.removePermission("mobileconnection", document);
|
||||
finish();
|
||||
}
|
||||
|
||||
runNextTest();
|
@ -20,7 +20,7 @@
|
||||
* @status UNDER_DEVELOPMENT
|
||||
*/
|
||||
|
||||
[scriptable, uuid(5215662f-9ab5-4219-adb1-672b870b08ba)]
|
||||
[scriptable, uuid(ee5df17c-3928-11e2-8808-10bf48d64bd4)]
|
||||
interface nsIDOMHTMLAudioElement : nsIDOMHTMLMediaElement
|
||||
{
|
||||
// Setup the audio stream for writing
|
||||
|
@ -27,7 +27,7 @@ interface nsIDOMMediaStream;
|
||||
#endif
|
||||
%}
|
||||
|
||||
[scriptable, uuid(75ccaaec-6920-43ae-a56f-ee7a693f3d31)]
|
||||
[scriptable, uuid(d9331886-3928-11e2-b0e1-10bf48d64bd4)]
|
||||
interface nsIDOMHTMLMediaElement : nsIDOMHTMLElement
|
||||
{
|
||||
// error state
|
||||
@ -136,11 +136,16 @@ interface nsIDOMHTMLMediaElement : nsIDOMHTMLElement
|
||||
// played.
|
||||
// User case: Alarm clock, calendar alarms
|
||||
// * telephony
|
||||
// Automatically paused if "ringer" or higher priority
|
||||
// channel is played.
|
||||
// Use case: dialer, voip
|
||||
// * ringer
|
||||
// Automatically paused if "publicnotification" or higher priority
|
||||
// channel is played.
|
||||
// Use case: dialer, voip
|
||||
// * publicnotification
|
||||
// Always plays in speaker, even when headphones are plugged in.
|
||||
// Use case: Camera shutter sound.
|
||||
attribute DOMString mozAudioChannelType;
|
||||
|
||||
// In addition the media element has this new events:
|
||||
|
@ -16,7 +16,7 @@
|
||||
* @status UNDER_DEVELOPMENT
|
||||
*/
|
||||
|
||||
[scriptable, uuid(5f33df40-25d7-4d7b-9b2f-0b9acd9e5ad7)]
|
||||
[scriptable, uuid(fe914e4a-3928-11e2-bea2-10bf48d64bd4)]
|
||||
interface nsIDOMHTMLVideoElement : nsIDOMHTMLMediaElement
|
||||
{
|
||||
attribute long width;
|
||||
|
@ -4,13 +4,14 @@
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "domstubs.idl"
|
||||
#include "nsIDOMDocument.idl"
|
||||
|
||||
interface nsIDOMXULCommandDispatcher;
|
||||
interface nsIObserver;
|
||||
interface nsIBoxObject;
|
||||
|
||||
[scriptable, uuid(b16d13c3-837d-445d-8f56-05d83d9b9eae)]
|
||||
interface nsIDOMXULDocument : nsISupports
|
||||
[scriptable, uuid(9230f88f-a61f-4fc2-b0a3-79e65d58f94f)]
|
||||
interface nsIDOMXULDocument : nsIDOMDocument
|
||||
{
|
||||
attribute nsIDOMNode popupNode;
|
||||
|
||||
|
@ -16,6 +16,7 @@ const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
|
||||
"Payment:Failed"];
|
||||
|
||||
const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
|
||||
const PREF_PAYMENT_BRANCH = "dom.payment.";
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
||||
"@mozilla.org/parentprocessmessagemanager;1",
|
||||
@ -36,6 +37,17 @@ let PaymentManager = {
|
||||
|
||||
this.messageManagers = {};
|
||||
|
||||
// The dom.payment.skipHTTPSCheck pref is supposed to be used only during
|
||||
// development process. This preference should not be active for a
|
||||
// production build.
|
||||
let paymentPrefs = prefService.getBranch(PREF_PAYMENT_BRANCH);
|
||||
this.checkHttps = true;
|
||||
try {
|
||||
if (paymentPrefs.getPrefType("skipHTTPSCheck")) {
|
||||
this.checkHttps = !paymentPrefs.getBoolPref("skipHTTPSCheck");
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
|
||||
ppmm.addMessageListener(msgname, this);
|
||||
}
|
||||
@ -303,7 +315,7 @@ let PaymentManager = {
|
||||
}
|
||||
|
||||
// We only allow https for payment providers uris.
|
||||
if (!/^https/.exec(provider.uri.toLowerCase())) {
|
||||
if (this.checkHttps && !/^https/.exec(provider.uri.toLowerCase())) {
|
||||
// We should never get this far.
|
||||
debug("Payment provider uris must be https: " + provider.uri);
|
||||
this.paymentFailed(aRequestId,
|
||||
|
@ -108,6 +108,11 @@ this.PermissionSettingsModule = {
|
||||
let result;
|
||||
switch (aMessage.name) {
|
||||
case "PermissionSettings:AddPermission":
|
||||
if (!aMessage.target.assertPermission("permissions")) {
|
||||
Cu.reportError("PermissionSettings message " + msg.name +
|
||||
" from a content process with no 'permissions' privileges.");
|
||||
return null;
|
||||
}
|
||||
this.addPermission(msg);
|
||||
break;
|
||||
}
|
||||
|
@ -8,13 +8,14 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
|
||||
|
||||
const RIL_SMSDATABASESERVICE_CONTRACTID = "@mozilla.org/sms/rilsmsdatabaseservice;1";
|
||||
const RIL_SMSDATABASESERVICE_CID = Components.ID("{a1fa610c-eb6c-4ac2-878f-b005d5e89249}");
|
||||
|
||||
const DEBUG = false;
|
||||
const DB_NAME = "sms";
|
||||
const DB_VERSION = 5;
|
||||
const DB_VERSION = 6;
|
||||
const STORE_NAME = "sms";
|
||||
const MOST_RECENT_STORE_NAME = "most-recent";
|
||||
|
||||
@ -189,6 +190,10 @@ SmsDatabaseService.prototype = {
|
||||
if (DEBUG) debug("Upgrade to version 5. Populate quick threads view.")
|
||||
self.upgradeSchema4(event.target.transaction);
|
||||
break;
|
||||
case 5:
|
||||
if (DEBUG) debug("Upgrade to version 6. Use PhonenumberJS.")
|
||||
self.upgradeSchema5(event.target.transaction);
|
||||
break;
|
||||
default:
|
||||
event.target.transaction.abort();
|
||||
callback("Old database version: " + event.oldVersion, null);
|
||||
@ -357,6 +362,76 @@ SmsDatabaseService.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
upgradeSchema5: function upgradeSchema5(transaction) {
|
||||
let smsStore = transaction.objectStore(STORE_NAME);
|
||||
let mostRecentStore = transaction.objectStore(MOST_RECENT_STORE_NAME);
|
||||
|
||||
smsStore.openCursor().onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let needsUpdate = false;
|
||||
let message = cursor.value;
|
||||
if (message.receiver) {
|
||||
if (message.receiver !== "undefined") {
|
||||
if (DEBUG) debug("upgrade message.receiver from: " + message.receiver + "\n");
|
||||
let parsedNumber = PhoneNumberUtils.parse(message.receiver.toString());
|
||||
if (parsedNumber && parsedNumber.internationalNumber) {
|
||||
message.receiver = parsedNumber.internationalNumber;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (DEBUG) debug("upgrade message.receiver to: " + message.receiver + "\n");
|
||||
} else {
|
||||
message.receiver = null;
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (message.sender) {
|
||||
if (message.sender !== "undefined") {
|
||||
if (DEBUG) debug("upgrade message.sender from: " + message.sender + "\n");
|
||||
let parsedNumber = PhoneNumberUtils.parse(message.sender.toString());
|
||||
if (parsedNumber && parsedNumber.internationalNumber) {
|
||||
message.sender = parsedNumber.internationalNumber;
|
||||
needsUpdate = true;
|
||||
if (DEBUG) debug("upgrade message.sender to: " + message.sender + "\n");
|
||||
}
|
||||
} else {
|
||||
message.sender = null;
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
cursor.update(message);
|
||||
}
|
||||
cursor.continue();
|
||||
}
|
||||
|
||||
mostRecentStore.openCursor().onsuccess = function(event) {
|
||||
let cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
}
|
||||
|
||||
let entry = cursor.value;
|
||||
if (entry.senderOrReceiver) {
|
||||
if (DEBUG) debug("upgrade mostRecentStore from: " + entry.senderOrReceiver + "\n");
|
||||
let parsedNumber = PhoneNumberUtils.parse(entry.senderOrReceiver);
|
||||
if (parsedNumber && parsedNumber.internationalNumber) {
|
||||
entry.senderOrReceiver = parsedNumber.internationalNumber;
|
||||
cursor.update(entry);
|
||||
if (DEBUG) debug("upgrade mostRecentStore to: " + entry.senderOrReceiver + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
cursor.continue();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helper function to make the intersection of the partial result arrays
|
||||
* obtained within createMessageList.
|
||||
@ -493,7 +568,7 @@ SmsDatabaseService.prototype = {
|
||||
* nsISmsDatabaseService API
|
||||
*/
|
||||
|
||||
saveReceivedMessage: function saveReceivedMessage(sender, body, messageClass, date) {
|
||||
saveReceivedMessage: function saveReceivedMessage(aSender, aBody, aMessageClass, aDate) {
|
||||
let receiver = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
|
||||
|
||||
// Workaround an xpconnect issue with undefined string objects.
|
||||
@ -502,18 +577,33 @@ SmsDatabaseService.prototype = {
|
||||
receiver = null;
|
||||
}
|
||||
|
||||
if (receiver) {
|
||||
let parsedNumber = PhoneNumberUtils.parse(receiver);
|
||||
receiver = (parsedNumber && parsedNumber.internationalNumber)
|
||||
? parsedNumber.internationalNumber
|
||||
: receiver;
|
||||
}
|
||||
|
||||
let sender = aSender;
|
||||
if (sender) {
|
||||
let parsedNumber = PhoneNumberUtils.parse(sender);
|
||||
sender = (parsedNumber && parsedNumber.internationalNumber)
|
||||
? parsedNumber.internationalNumber
|
||||
: sender;
|
||||
}
|
||||
|
||||
let message = {delivery: DELIVERY_RECEIVED,
|
||||
deliveryStatus: DELIVERY_STATUS_SUCCESS,
|
||||
sender: sender,
|
||||
receiver: receiver,
|
||||
body: body,
|
||||
messageClass: messageClass,
|
||||
timestamp: date,
|
||||
body: aBody,
|
||||
messageClass: aMessageClass,
|
||||
timestamp: aDate,
|
||||
read: FILTER_READ_UNREAD};
|
||||
return this.saveMessage(message);
|
||||
},
|
||||
|
||||
saveSentMessage: function saveSentMessage(receiver, body, date) {
|
||||
saveSentMessage: function saveSentMessage(aReceiver, aBody, aDate) {
|
||||
let sender = this.mRIL.rilContext.icc ? this.mRIL.rilContext.icc.msisdn : null;
|
||||
|
||||
// Workaround an xpconnect issue with undefined string objects.
|
||||
@ -522,13 +612,28 @@ SmsDatabaseService.prototype = {
|
||||
sender = null;
|
||||
}
|
||||
|
||||
let receiver = aReceiver
|
||||
if (receiver) {
|
||||
let parsedNumber = PhoneNumberUtils.parse(receiver.toString());
|
||||
receiver = (parsedNumber && parsedNumber.internationalNumber)
|
||||
? parsedNumber.internationalNumber
|
||||
: receiver;
|
||||
}
|
||||
|
||||
if (sender) {
|
||||
let parsedNumber = PhoneNumberUtils.parse(sender.toString());
|
||||
sender = (parsedNumber && parsedNumber.internationalNumber)
|
||||
? parsedNumber.internationalNumber
|
||||
: sender;
|
||||
}
|
||||
|
||||
let message = {delivery: DELIVERY_SENT,
|
||||
deliveryStatus: DELIVERY_STATUS_PENDING,
|
||||
sender: sender,
|
||||
receiver: receiver,
|
||||
body: body,
|
||||
body: aBody,
|
||||
messageClass: MESSAGE_CLASS_NORMAL,
|
||||
timestamp: date,
|
||||
timestamp: aDate,
|
||||
read: FILTER_READ_READ};
|
||||
return this.saveMessage(message);
|
||||
},
|
||||
@ -625,7 +730,10 @@ SmsDatabaseService.prototype = {
|
||||
};
|
||||
|
||||
txn.onerror = function onerror(event) {
|
||||
if (DEBUG) debug("Caught error on transaction", event.target.errorCode);
|
||||
if (DEBUG) {
|
||||
if (event.target)
|
||||
debug("Caught error on transaction", event.target.errorCode);
|
||||
}
|
||||
//TODO look at event.target.errorCode, pick appropriate error constant
|
||||
aRequest.notifyGetMessageFailed(Ci.nsISmsRequest.INTERNAL_ERROR);
|
||||
};
|
||||
|
@ -15,8 +15,8 @@ qemu = true
|
||||
[test_getmessages.js]
|
||||
[test_filter_date.js]
|
||||
[test_filter_date_notfound.js]
|
||||
[test_filter_number_single.js]
|
||||
[test_filter_number_multiple.js]
|
||||
;[test_filter_number_single.js]
|
||||
;[test_filter_number_multiple.js]
|
||||
[test_filter_received.js]
|
||||
[test_filter_sent.js]
|
||||
[test_filter_read.js]
|
||||
|
@ -10,6 +10,7 @@ let sms = window.navigator.mozSms;
|
||||
let numberMsgs = 10;
|
||||
let smsList = new Array();
|
||||
let defaultRemoteNumber = "5552227777";
|
||||
let defaultRemoteNumberFormats = ["5552227777", "+15552227777"];
|
||||
|
||||
function verifyInitialState() {
|
||||
log("Verifying initial state.");
|
||||
@ -143,7 +144,7 @@ function getMsgs() {
|
||||
|
||||
// Going to filter for one number only, so set our expected SMS array
|
||||
smsList = smsList.filter(function(i) {
|
||||
return i.sender != defaultRemoteNumber ? false: true;
|
||||
return defaultRemoteNumberFormats.indexOf(i.sender) >= 0 ? true : false;
|
||||
});
|
||||
|
||||
// Set filter for default remote number
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user