Merge last PGO-green changeset of mozilla-inbound to mozilla-central

This commit is contained in:
Ed Morley 2012-12-07 14:14:03 +00:00
commit c48dc553b3
224 changed files with 5941 additions and 2461 deletions

View File

@ -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();
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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 {

View File

@ -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

View File

@ -274,6 +274,7 @@ _BROWSER_FILES = \
browser_bug734076.js \
browser_bug812562.js \
browser_bug818009.js \
browser_bug818118.js \
blockPluginVulnerableUpdatable.xml \
blockPluginVulnerableNoUpdate.xml \
blockNoPlugins.xml \

View 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();
}

View File

@ -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) {

View File

@ -26,6 +26,7 @@ EXTRA_JS_MODULES = \
webappsUI.jsm \
webrtcUI.jsm \
KeywordURLResetPrompter.jsm \
SharedFrame.jsm \
$(NULL)
EXTRA_PP_JS_MODULES = RecentWindow.jsm

View 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;
}
}

View File

@ -8,6 +8,10 @@ srcdir = @srcdir@
VPATH = @srcdir@
relativesrcdir = @relativesrcdir@
DIRS = \
chrome \
$(NULL)
include $(DEPTH)/config/autoconf.mk
XPCSHELL_TESTS = unit

View 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

View 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>

View 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>

View File

@ -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");
}
}

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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; }

View File

@ -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)

View File

@ -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)
{

View File

@ -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.

View 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_

View File

@ -24,6 +24,12 @@ EXPORTS = \
nsVKList.h \
$(NULL)
EXPORTS_NAMESPACES = mozilla/dom
EXPORTS_mozilla/dom = \
EventTarget.h \
$(NULL)
XPIDLSRCS = \
nsIEventListenerService.idl \
$(NULL)

View File

@ -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)
{

View File

@ -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

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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:

View 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

View File

@ -18,6 +18,7 @@ EXPORTS = \
AbstractMediaDecoder.h \
AudioSampleFormat.h \
AudioSegment.h \
BufferMediaResource.h \
DecoderTraits.h \
FileBlockCache.h \
MediaDecoderOwner.h \

View File

@ -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);

View File

@ -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.

View File

@ -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

View File

@ -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;
}
}

View File

@ -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).

View File

@ -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.

View File

@ -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()
{

View File

@ -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,

View File

@ -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
{

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View 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*/

View File

@ -23,6 +23,7 @@ EXPORTS := \
DASHDecoder.h \
DASHRepDecoder.h \
DASHReader.h \
DASHRepReader.h \
$(NULL)
CPPSRCS := \

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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() {

View 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,&lt;iframe&gt;&lt;/iframe&gt;"></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>

View File

@ -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

View 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));
}
}

View 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

View File

@ -18,6 +18,7 @@ enum AudioChannelType {
AUDIO_CHANNEL_NOTIFICATION,
AUDIO_CHANNEL_ALARM,
AUDIO_CHANNEL_TELEPHONY,
AUDIO_CHANNEL_RINGER,
AUDIO_CHANNEL_PUBLICNOTIFICATION,
AUDIO_CHANNEL_LAST
};

View File

@ -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

View File

@ -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;

View File

@ -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) {

View File

@ -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.

View File

@ -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

View 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);
};

View File

@ -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);

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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')

View File

@ -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) {

View File

@ -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)

View File

@ -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()

View File

@ -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));

View File

@ -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);

View File

@ -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
View 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

View File

@ -0,0 +1,6 @@
[DEFAULT]
b2g = true
browser = false
qemu = true
[test_stk_proactive_command.js]

View 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();

View File

@ -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

View File

@ -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:

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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;
}

View File

@ -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);
};

View File

@ -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]

View File

@ -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