merge mozilla-inbound to mozilla-central a=merge

This commit is contained in:
Carsten "Tomcat" Book 2015-09-01 14:31:50 +02:00
commit 4d2157e2f8
173 changed files with 3982 additions and 1050 deletions

View File

@ -29,21 +29,23 @@ let gSkipEmptyImages = new PrefCache('accessibility.accessfu.skip_empty_images')
function BaseTraversalRule(aRoles, aMatchFunc, aPreFilter) {
this._explicitMatchRoles = new Set(aRoles);
this._matchRoles = aRoles;
if (aRoles.indexOf(Roles.LABEL) < 0) {
this._matchRoles.push(Roles.LABEL);
}
if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
// Used for traversing in to child OOP frames.
this._matchRoles.push(Roles.INTERNAL_FRAME);
if (aRoles.length) {
if (aRoles.indexOf(Roles.LABEL) < 0) {
this._matchRoles.push(Roles.LABEL);
}
if (aRoles.indexOf(Roles.INTERNAL_FRAME) < 0) {
// Used for traversing in to child OOP frames.
this._matchRoles.push(Roles.INTERNAL_FRAME);
}
}
this._matchFunc = aMatchFunc || function() { return Filters.MATCH; };
this.preFilter = aPreFilter || gSimplePreFilter;
}
BaseTraversalRule.prototype = {
getMatchRoles: function BaseTraversalRule_getmatchRoles(aRules) {
aRules.value = this._matchRoles;
return aRules.value.length;
getMatchRoles: function BaseTraversalRule_getmatchRoles(aRoles) {
aRoles.value = this._matchRoles;
return aRoles.value.length;
},
match: function BaseTraversalRule_match(aAccessible)
@ -54,8 +56,9 @@ BaseTraversalRule.prototype = {
Filters.MATCH | Filters.IGNORE_SUBTREE : Filters.IGNORE;
}
let matchResult = this._explicitMatchRoles.has(role) ?
this._matchFunc(aAccessible) : Filters.IGNORE;
let matchResult =
(this._explicitMatchRoles.has(role) || !this._explicitMatchRoles.size) ?
this._matchFunc(aAccessible) : Filters.IGNORE;
// If we are on a label that nests a checkbox/radio we should land on it.
// It is a bigger touch target, and it reduces clutter.

View File

@ -10,55 +10,58 @@
</style>
</head>
<body>
<h3 id="heading-1">A small first heading</h3>
<form>
<label for="input-1-1">Name:</label>
<input id="input-1-1">
<label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label>
<button id="button-1-1">Magic Button</button>
<label for="radio-1-1">Radios are old: </label>
<input id="radio-1-1" type="radio">
<label for="radio-1-2">Radios are new: </label>
<input id="radio-1-2" type="radio">
<label for="input-1-3">Password:</label>
<input id="input-1-3" type="password">
<label for="input-1-4">Unlucky number:</label>
<input id="input-1-4" type="tel">
<input id="button-1-2" type="button" value="Fun">
<label for="checkbox-1-1">Check me: </label>
<input id="checkbox-1-1" type="checkbox">
<select id="select-1-1">
<option>Value 1</option>
<option>Value 2</option>
<option>Value 3</option>
</select>
<select id="select-1-2" multiple="true">
<option>Value 1</option>
<option>Value 2</option>
<option>Value 3</option>
</select>
<label for="checkbox-1-2">Check me too: </label>
<input id="checkbox-1-2" type="checkbox">
<label for="checkbox-1-3">But not me: </label>
<input id="checkbox-1-3" type="checkbox" aria-hidden="true">
<label for="checkbox-1-4">Or me! </label>
<input id="checkbox-1-4" type="checkbox" style="visibility:hidden">
<select id="select-1-3" size="3">
<option>Value 1</option>
<option>Value 2</option>
<option>Value 3</option>
</select>
<label for="input-1-5">Electronic mailing address:</label>
<input id="input-1-5" type="email">
<input id="button-1-3" type="submit" value="Submit">
</form>
<h2 id="heading-2">A larger second</h2>
<div id="heading-3" role="heading">ARIA is fun</div>
<input id="button-2-1" type="button" value="More Fun">
<div id="button-2-2" tabindex="0" role="button">ARIA fun</div>
<div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div>
<div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div>
<header id="header-1">
<h3 id="heading-1">A small first heading</h3>
<form>
<label for="input-1-1">Name:</label>
<input id="input-1-1">
<label id="label-1-2">Favourite Ice Cream Flavour:<input id="input-1-2"></label>
<button id="button-1-1">Magic Button</button>
<label for="radio-1-1">Radios are old: </label>
<input id="radio-1-1" type="radio">
<label for="radio-1-2">Radios are new: </label>
<input id="radio-1-2" type="radio">
<label for="input-1-3">Password:</label>
<input id="input-1-3" type="password">
<label for="input-1-4">Unlucky number:</label>
<input id="input-1-4" type="tel">
<input id="button-1-2" type="button" value="Fun">
<label for="checkbox-1-1">Check me: </label>
<input id="checkbox-1-1" type="checkbox">
<select id="select-1-1">
<option>Value 1</option>
<option>Value 2</option>
<option>Value 3</option>
</select>
<select id="select-1-2" multiple="true">
<option>Value 1</option>
<option>Value 2</option>
<option>Value 3</option>
</select>
<label for="checkbox-1-2">Check me too: </label>
<input id="checkbox-1-2" type="checkbox">
<label for="checkbox-1-3">But not me: </label>
<input id="checkbox-1-3" type="checkbox" aria-hidden="true">
<label for="checkbox-1-4">Or me! </label>
<input id="checkbox-1-4" type="checkbox" style="visibility:hidden">
<select id="select-1-3" size="3">
<option>Value 1</option>
<option>Value 2</option>
<option>Value 3</option>
</select>
<label for="input-1-5">Electronic mailing address:</label>
<input id="input-1-5" type="email">
<input id="button-1-3" type="submit" value="Submit">
</form>
</header>
<main id="main-1">
<h2 id="heading-2">A larger second</h2>
<div id="heading-3" role="heading">ARIA is fun</div>
<input id="button-2-1" type="button" value="More Fun">
<div id="button-2-2" tabindex="0" role="button">ARIA fun</div>
<div id="button-2-3" tabindex="0" role="button" aria-pressed="false">My little togglebutton</div>
<div id="button-2-4" tabindex="0" role="spinbutton">ARIA fun</div>
</main>
<h1 id="heading-4" style="display:none">Invisible header</h1>
<dl id="list-1">
<dt id="listitem-1-1">Programming Language</dt>
@ -141,8 +144,10 @@
</tr>
</tbody>
</table>
<div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
<div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
<footer id="footer-1">
<div id="statusbar-1" role="status">Last sync:<span>2 days ago</span></div>
<div aria-label="Last sync: 30min ago" id="statusbar-2" role="status"></div>
</footer>
<span id="switch-1" role="switch" aria-checked="false" aria-label="Light switch"></span>
<p>This is a MathML formula <math id="math-1" display="block">

View File

@ -127,6 +127,9 @@
'switch-1', 'This is a MathML formula ', 'math-1',
'with some text after.']);
queueTraversalSequence(gQueue, docAcc, TraversalRules.Landmark, null,
['header-1', 'main-1', 'footer-1']);
gQueue.invoke();
}

View File

@ -7577,6 +7577,17 @@ function restoreLastSession() {
var TabContextMenu = {
contextTab: null,
_updateToggleMuteMenuItem(aTab, aConditionFn) {
["muted", "soundplaying"].forEach(attr => {
if (!aConditionFn || aConditionFn(attr)) {
if (aTab.hasAttribute(attr)) {
aTab.toggleMuteMenuItem.setAttribute(attr, "true");
} else {
aTab.toggleMuteMenuItem.removeAttribute(attr);
}
}
});
},
updateContextMenu: function updateContextMenu(aPopupMenu) {
this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
aPopupMenu.triggerNode : gBrowser.selectedTab;
@ -7637,6 +7648,25 @@ var TabContextMenu = {
toggleMute.label = gNavigatorBundle.getString("muteTab.label");
toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
}
this.contextTab.toggleMuteMenuItem = toggleMute;
this._updateToggleMuteMenuItem(this.contextTab);
this.contextTab.addEventListener("TabAttrModified", this, false);
aPopupMenu.addEventListener("popuphiding", this, false);
},
handleEvent(aEvent) {
switch (aEvent.type) {
case "popuphiding":
gBrowser.removeEventListener("TabAttrModified", this);
aEvent.target.removeEventListener("popuphiding", this);
break;
case "TabAttrModified":
let tab = aEvent.target;
this._updateToggleMuteMenuItem(tab,
attr => aEvent.detail.changed.indexOf(attr) >= 0);
break;
}
}
};

View File

@ -3958,10 +3958,18 @@
stringWithShortcut("tabs.closeSelectedTab.tooltip", "key_close") :
this.mStringBundle.getString("tabs.closeTab.tooltip");
} else if (tab._overPlayingIcon) {
let stringID = tab.linkedBrowser.audioMuted ?
"tabs.unmuteAudio.tooltip" :
"tabs.muteAudio.tooltip";
label = stringWithShortcut(stringID, "key_toggleMute");
let stringID;
if (tab.selected) {
stringID = tab.linkedBrowser.audioMuted ?
"tabs.unmuteAudio.tooltip" :
"tabs.muteAudio.tooltip";
label = stringWithShortcut(stringID, "key_toggleMute");
} else {
stringID = tab.linkedBrowser.audioMuted ?
"tabs.unmuteAudio.background.tooltip" :
"tabs.muteAudio.background.tooltip";
label = this.mStringBundle.getString(stringID);
}
} else {
label = tab.getAttribute("label") +
(this.AppConstants.E10S_TESTING_ONLY && tab.linkedBrowser && tab.linkedBrowser.isRemoteBrowser ? " - e10s" : "");

View File

@ -10,6 +10,26 @@ function* wait_for_tab_playing_event(tab, expectPlaying) {
});
}
function* play(tab) {
let browser = tab.linkedBrowser;
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
yield wait_for_tab_playing_event(tab, true);
}
function* pause(tab) {
let browser = tab.linkedBrowser;
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.pause();
});
yield wait_for_tab_playing_event(tab, false);
}
function disable_non_test_mouse(disable) {
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
@ -36,11 +56,19 @@ function leave_icon(icon) {
disable_non_test_mouse(false);
}
function* test_tooltip(icon, expectedTooltip) {
function* test_tooltip(icon, expectedTooltip, isActiveTab) {
let tooltip = document.getElementById("tabbrowser-tab-tooltip");
yield hover_icon(icon, tooltip);
is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
if (isActiveTab) {
// The active tab should have the keybinding shortcut in the tooltip.
// We check this by ensuring that the strings are not equal but the expected
// message appears in the beginning.
isnot(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
is(tooltip.getAttribute("label").indexOf(expectedTooltip), 0, "Correct tooltip expected");
} else {
is(tooltip.getAttribute("label"), expectedTooltip, "Tooltips should not be equal");
}
leave_icon(icon);
}
@ -89,6 +117,19 @@ function* test_muting_using_menu(tab, expectMuted) {
is(toggleMute.label, expectedLabel, "Correct label expected");
is(toggleMute.accessKey, "M", "Correct accessKey expected");
is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
yield play(tab);
is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
ok(toggleMute.hasAttribute("soundplaying"), "Should have the soundplaying attribute");
yield pause(tab);
is(toggleMute.hasAttribute("muted"), expectMuted, "Should have the correct state for the muted attribute");
ok(!toggleMute.hasAttribute("soundplaying"), "Should not have the soundplaying attribute");
// Click on the menu and wait for the tab to be muted.
let mutedPromise = get_wait_for_mute_promise(tab, !expectMuted);
let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
@ -100,15 +141,11 @@ function* test_muting_using_menu(tab, expectMuted) {
function* test_playing_icon_on_tab(tab, browser, isPinned) {
let icon = document.getAnonymousElementByAttribute(tab, "anonid",
isPinned ? "overlay-icon" : "soundplaying-icon");
let isActiveTab = tab === gBrowser.selectedTab;
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
yield play(tab);
yield wait_for_tab_playing_event(tab, true);
yield test_tooltip(icon, "Mute tab");
yield test_tooltip(icon, "Mute tab", isActiveTab);
ok(!("muted" in get_tab_attributes(tab)), "No muted attribute should be persisted");
@ -116,26 +153,22 @@ function* test_playing_icon_on_tab(tab, browser, isPinned) {
ok("muted" in get_tab_attributes(tab), "Muted attribute should be persisted");
yield test_tooltip(icon, "Unmute tab");
yield test_tooltip(icon, "Unmute tab", isActiveTab);
yield test_mute_tab(tab, icon, false);
ok(!("muted" in get_tab_attributes(tab)), "No muted attribute should be persisted");
yield test_tooltip(icon, "Mute tab");
yield test_tooltip(icon, "Mute tab", isActiveTab);
yield test_mute_tab(tab, icon, true);
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.pause();
});
yield wait_for_tab_playing_event(tab, false);
yield pause(tab);
ok(tab.hasAttribute("muted") &&
!tab.hasAttribute("soundplaying"), "Tab should still be muted but not playing");
yield test_tooltip(icon, "Unmute tab");
yield test_tooltip(icon, "Unmute tab", isActiveTab);
yield test_mute_tab(tab, icon, false);
@ -184,11 +217,7 @@ function* test_swapped_browser(oldTab, newBrowser, isPlaying) {
function* test_browser_swapping(tab, browser) {
// First, test swapping with a playing but muted tab.
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
yield wait_for_tab_playing_event(tab, true);
yield play(tab);
let icon = document.getAnonymousElementByAttribute(tab, "anonid",
"soundplaying-icon");
@ -203,10 +232,7 @@ function* test_browser_swapping(tab, browser) {
// Now, test swapping with a muted but not playing tab.
// Note that the tab remains muted, so we only need to pause playback.
tab = gBrowser.getTabForBrowser(newBrowser);
yield ContentTask.spawn(newBrowser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.pause();
});
yield pause(tab);
yield BrowserTestUtils.withNewTab({
gBrowser,
@ -226,24 +252,15 @@ function* test_click_on_pinned_tab_after_mute() {
// Pin the tab.
gBrowser.pinTab(tab);
// Start playbak.
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
// Wait for playback to start.
yield wait_for_tab_playing_event(tab, true);
// Start playback and wait for it to finish.
yield play(tab);
// Mute the tab.
let icon = document.getAnonymousElementByAttribute(tab, "anonid", "overlay-icon");
yield test_mute_tab(tab, icon, true);
// Stop playback
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.pause();
});
// Pause playback and wait for it to finish.
yield pause(tab);
// Unmute tab.
yield test_mute_tab(tab, icon, false);
@ -272,14 +289,8 @@ function* test_cross_process_load() {
function* test_on_browser(browser) {
let tab = gBrowser.getTabForBrowser(browser);
// Start playback.
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
// Wait for playback to start.
yield wait_for_tab_playing_event(tab, true);
// Start playback and wait for it to finish.
yield play(tab);
let soundPlayingStoppedPromise = BrowserTestUtils.waitForEvent(tab, "TabAttrModified", false,
event => event.detail.changed.indexOf("soundplaying") >= 0
@ -315,23 +326,14 @@ function* test_mute_keybinding() {
// Make sure it's possible to mute before the tab is playing.
yield test_muting_using_keyboard(tab);
// Start playback.
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
// Wait for playback to start.
yield wait_for_tab_playing_event(tab, true);
// Start playback and wait for it to finish.
yield play(tab);
// Make sure it's possible to mute after the tab is playing.
yield test_muting_using_keyboard(tab);
// Start playback.
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.pause();
});
// Pause playback and wait for it to finish.
yield pause(tab);
// Make sure things work if the tab is pinned.
gBrowser.pinTab(tab);
@ -339,14 +341,8 @@ function* test_mute_keybinding() {
// Make sure it's possible to mute before the tab is playing.
yield test_muting_using_keyboard(tab);
// Start playback.
yield ContentTask.spawn(browser, {}, function* () {
let audio = content.document.querySelector("audio");
audio.play();
});
// Wait for playback to start.
yield wait_for_tab_playing_event(tab, true);
// Start playback and wait for it to finish.
yield play(tab);
// Make sure it's possible to mute after the tab is playing.
yield test_muting_using_keyboard(tab);

View File

@ -40,3 +40,5 @@ tabs.muteAudio.tooltip=Mute tab (%S)
# LOCALIZATION NOTE (tabs.unmuteAudio.tooltip):
# %S is the keyboard shortcut for "Unmute tab"
tabs.unmuteAudio.tooltip=Unmute tab (%S)
tabs.muteAudio.background.tooltip=Mute tab
tabs.unmuteAudio.background.tooltip=Unmute tab

View File

@ -4970,6 +4970,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI,
case NS_ERROR_INTERCEPTED_ERROR_RESPONSE:
case NS_ERROR_INTERCEPTED_USED_RESPONSE:
case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION:
case NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION:
// ServiceWorker intercepted request, but something went wrong.
nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(),
aError);
@ -10563,6 +10564,8 @@ nsDocShell::DoURILoad(nsIURI* aURI,
} else {
httpChannelInternal->SetDocumentURI(aReferrerURI);
}
httpChannelInternal->SetRedirectMode(
nsIHttpChannelInternal::REDIRECT_MODE_MANUAL);
}
nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel));

View File

@ -679,8 +679,8 @@ nsIContent::PreHandleEvent(EventChainPreVisitor& aVisitor)
bool isAnonForEvents = IsRootOfChromeAccessOnlySubtree();
if ((aVisitor.mEvent->mMessage == eMouseOver ||
aVisitor.mEvent->mMessage == eMouseOut ||
aVisitor.mEvent->mMessage == NS_POINTER_OVER ||
aVisitor.mEvent->mMessage == NS_POINTER_OUT) &&
aVisitor.mEvent->mMessage == ePointerOver ||
aVisitor.mEvent->mMessage == ePointerOut) &&
// Check if we should stop event propagation when event has just been
// dispatched or when we're about to propagate from
// chrome access only subtree or if we are about to propagate out of

View File

@ -3458,6 +3458,8 @@ nsContentUtils::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument,
messageName = "InterceptedUsedResponse";
} else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) {
messageName = "ClientRequestOpaqueInterception";
} else if (aError == NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION) {
messageName = "BadOpaqueRedirectInterception";
}
if (messageName) {
@ -7848,7 +7850,7 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<nsIPresShell> aPresShell,
else if (aType.EqualsLiteral("mouseout"))
msg = eMouseExitFromWidget;
else if (aType.EqualsLiteral("contextmenu")) {
msg = NS_CONTEXTMENU;
msg = eContextMenu;
contextMenuKey = (aButton == 0);
} else if (aType.EqualsLiteral("MozMouseHittest"))
msg = eMouseHitTest;

View File

@ -1312,6 +1312,8 @@ public:
* are not converted into newlines. Only textnodes and cdata nodes are
* added to the result.
*
* @see nsLayoutUtils::GetFrameTextContent
*
* @param aNode Node to get textual contents of.
* @param aDeep If true child elements of aNode are recursivly descended
* into to find text children.

View File

@ -702,15 +702,15 @@ nsDOMWindowUtils::SendPointerEventCommon(const nsAString& aType,
EventMessage msg;
if (aType.EqualsLiteral("pointerdown")) {
msg = NS_POINTER_DOWN;
msg = ePointerDown;
} else if (aType.EqualsLiteral("pointerup")) {
msg = NS_POINTER_UP;
msg = ePointerUp;
} else if (aType.EqualsLiteral("pointermove")) {
msg = NS_POINTER_MOVE;
msg = ePointerMove;
} else if (aType.EqualsLiteral("pointerover")) {
msg = NS_POINTER_OVER;
msg = ePointerOver;
} else if (aType.EqualsLiteral("pointerout")) {
msg = NS_POINTER_OUT;
msg = ePointerOut;
} else {
return NS_ERROR_FAILURE;
}

View File

@ -1736,14 +1736,15 @@ public:
*/
void SetDisplayDocument(nsIDocument* aDisplayDocument)
{
NS_PRECONDITION(!GetShell() &&
!GetContainer() &&
!GetWindow(),
"Shouldn't set mDisplayDocument on documents that already "
"have a presentation or a docshell or a window");
NS_PRECONDITION(aDisplayDocument != this, "Should be different document");
NS_PRECONDITION(!aDisplayDocument->GetDisplayDocument(),
"Display documents should not nest");
MOZ_ASSERT(!GetShell() &&
!GetContainer() &&
!GetWindow(),
"Shouldn't set mDisplayDocument on documents that already "
"have a presentation or a docshell or a window");
MOZ_ASSERT(aDisplayDocument, "Must not be null");
MOZ_ASSERT(aDisplayDocument != this, "Should be different document");
MOZ_ASSERT(!aDisplayDocument->GetDisplayDocument(),
"Display documents should not nest");
mDisplayDocument = aDisplayDocument;
mHasDisplayDocument = !!aDisplayDocument;
}
@ -1764,7 +1765,7 @@ public:
virtual ~ExternalResourceLoad() {}
void AddObserver(nsIObserver* aObserver) {
NS_PRECONDITION(aObserver, "Must have observer");
MOZ_ASSERT(aObserver, "Must have observer");
mObservers.AppendElement(aObserver);
}

View File

@ -267,6 +267,8 @@ support-files =
skip-if = buildapp == 'mulet'
[test_audioNotificationStopOnNavigation.html]
skip-if = buildapp == 'mulet'
[test_audioNotificationWithEarlyPlay.html]
skip-if = buildapp == 'mulet'
[test_bug1091883.html]
[test_bug116083.html]
[test_bug793311.html]

View File

@ -0,0 +1,73 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test for audio controller in windows</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>
<pre id="test">
</pre>
<script type="application/javascript">
SimpleTest.waitForExplicitFinish();
var expectedNotification = null;
var observer = {
observe: function(subject, topic, data) {
is(topic, "audio-playback", "audio-playback received");
is(data, expectedNotification, "This is the right notification");
runTest();
}
};
var observerService = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
.getService(SpecialPowers.Ci.nsIObserverService);
var audio = new Audio();
audio.loop = true;
audio.preload = "metadata";
var tests = [
function() {
observerService.addObserver(observer, "audio-playback", false);
ok(true, "Observer set");
runTest();
},
function() {
expectedNotification = 'active';
audio.src = "audio.ogg";
audio.play();
},
function() {
expectedNotification = 'inactive';
audio.pause();
},
function() {
observerService.removeObserver(observer, "audio-playback");
ok(true, "Observer removed");
runTest();
}
];
function runTest() {
if (!tests.length) {
SimpleTest.finish();
return;
}
var test = tests.shift();
test();
}
runTest();
</script>
</body>
</html>

View File

@ -239,6 +239,41 @@ CacheStorage::CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
return ref.forget();
}
// static
bool
CacheStorage::DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL,
"Passed object is not a global object!");
if (NS_WARN_IF(!CacheStorageBinding::GetConstructorObject(aCx, aGlobal) ||
!CacheBinding::GetConstructorObject(aCx, aGlobal))) {
return false;
}
nsIPrincipal* principal = nsContentUtils::ObjectPrincipal(aGlobal);
MOZ_ASSERT(principal);
ErrorResult rv;
nsRefPtr<CacheStorage> storage =
CreateOnMainThread(DEFAULT_NAMESPACE, xpc::NativeGlobal(aGlobal), principal,
false, /* private browsing */
true, /* force trusted */
rv);
if (NS_WARN_IF(rv.Failed())) {
return ThrowMethodFailed(aCx, rv);
}
JS::Rooted<JS::Value> caches(aCx);
js::AssertSameCompartment(aCx, aGlobal);
if (NS_WARN_IF(!ToJSValue(aCx, storage, &caches))) {
return false;
}
return JS_DefineProperty(aCx, aGlobal, "caches", caches, JSPROP_ENUMERATE);
}
CacheStorage::CacheStorage(Namespace aNamespace, nsIGlobalObject* aGlobal,
const PrincipalInfo& aPrincipalInfo, Feature* aFeature)
: mNamespace(aNamespace)

View File

@ -56,6 +56,9 @@ public:
CreateOnWorker(Namespace aNamespace, nsIGlobalObject* aGlobal,
workers::WorkerPrivate* aWorkerPrivate, ErrorResult& aRv);
static bool
DefineCaches(JSContext* aCx, JS::Handle<JSObject*> aGlobal);
// webidl interface methods
already_AddRefed<Promise> Match(const RequestOrUSVString& aRequest,
const CacheQueryOptions& aOptions,

View File

@ -13,6 +13,7 @@ using HeadersGuardEnum from "mozilla/dom/cache/IPCUtils.h";
using RequestCredentials from "mozilla/dom/cache/IPCUtils.h";
using RequestMode from "mozilla/dom/cache/IPCUtils.h";
using RequestCache from "mozilla/dom/cache/IPCUtils.h";
using RequestRedirect from "mozilla/dom/cache/IPCUtils.h";
using ResponseType from "mozilla/dom/cache/IPCUtils.h";
using mozilla::void_t from "ipc/IPCMessageUtils.h";
using struct nsID from "nsID.h";
@ -64,6 +65,7 @@ struct CacheRequest
CacheReadStreamOrVoid body;
uint32_t contentPolicyType;
RequestCache requestCache;
RequestRedirect requestRedirect;
};
union CacheRequestOrVoid

View File

@ -170,7 +170,7 @@ DBAction::OpenConnection(const QuotaInfo& aQuotaInfo, nsIFile* aDBDir,
int32_t schemaVersion = 0;
rv = conn->GetSchemaVersion(&schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (schemaVersion > 0 && schemaVersion < db::kMaxWipeSchemaVersion) {
if (schemaVersion > 0 && schemaVersion < db::kFirstShippedSchemaVersion) {
conn = nullptr;
rv = WipeDatabase(dbFile, aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }

529
dom/cache/DBSchema.cpp vendored
View File

@ -14,6 +14,7 @@
#include "mozilla/dom/cache/TypeUtils.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageHelper.h"
#include "nsCOMPtr.h"
#include "nsTArray.h"
#include "nsCRT.h"
@ -31,11 +32,135 @@ namespace dom {
namespace cache {
namespace db {
const int32_t kMaxWipeSchemaVersion = 15;
const int32_t kFirstShippedSchemaVersion = 15;
namespace {
const int32_t kLatestSchemaVersion = 15;
// Update this whenever the DB schema is changed.
const int32_t kLatestSchemaVersion = 16;
// ---------
// The following constants define the SQL schema. These are defined in the
// same order the SQL should be executed in CreateOrMigrateSchema(). They are
// broken out as constants for convenient use in validation and migration.
// ---------
// The caches table is the single source of truth about what Cache
// objects exist for the origin. The contents of the Cache are stored
// in the entries table that references back to caches.
//
// The caches table is also referenced from storage. Rows in storage
// represent named Cache objects. There are cases, however, where
// a Cache can still exist, but not be in a named Storage. For example,
// when content is still using the Cache after CacheStorage::Delete()
// has been run.
//
// For now, the caches table mainly exists for data integrity with
// foreign keys, but could be expanded to contain additional cache object
// information.
//
// AUTOINCREMENT is necessary to prevent CacheId values from being reused.
const char* const kTableCaches =
"CREATE TABLE caches ("
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT "
")";
// Security blobs are quite large and duplicated for every Response from
// the same https origin. This table is used to de-duplicate this data.
const char* const kTableSecurityInfo =
"CREATE TABLE security_info ("
"id INTEGER NOT NULL PRIMARY KEY, "
"hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column
"data BLOB NOT NULL, " // full security info data, usually a few KB
"refcount INTEGER NOT NULL"
")";
// Index the smaller hash value instead of the large security data blob.
const char* const kIndexSecurityInfoHash =
"CREATE INDEX security_info_hash_index ON security_info (hash)";
const char* const kTableEntries =
"CREATE TABLE entries ("
"id INTEGER NOT NULL PRIMARY KEY, "
"request_method TEXT NOT NULL, "
"request_url_no_query TEXT NOT NULL, "
"request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash
"request_url_query TEXT NOT NULL, "
"request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash
"request_referrer TEXT NOT NULL, "
"request_headers_guard INTEGER NOT NULL, "
"request_mode INTEGER NOT NULL, "
"request_credentials INTEGER NOT NULL, "
"request_contentpolicytype INTEGER NOT NULL, "
"request_cache INTEGER NOT NULL, "
"request_body_id TEXT NULL, "
"response_type INTEGER NOT NULL, "
"response_url TEXT NOT NULL, "
"response_status INTEGER NOT NULL, "
"response_status_text TEXT NOT NULL, "
"response_headers_guard INTEGER NOT NULL, "
"response_body_id TEXT NULL, "
"response_security_info_id INTEGER NULL REFERENCES security_info(id), "
"response_principal_info TEXT NOT NULL, "
"response_redirected INTEGER NOT NULL, "
// Note that response_redirected_url is either going to be empty, or
// it's going to be a URL different than response_url.
"response_redirected_url TEXT NOT NULL, "
"cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE, "
// New columns must be added at the end of table to migrate and
// validate properly.
"request_redirect INTEGER NOT NULL"
")";
// Create an index to support the QueryCache() matching algorithm. This
// needs to quickly find entries in a given Cache that match the request
// URL. The url query is separated in order to support the ignoreSearch
// option. Finally, we index hashes of the URL values instead of the
// actual strings to avoid excessive disk bloat. The index will duplicate
// the contents of the columsn in the index. The hash index will prune
// the vast majority of values from the query result so that normal
// scanning only has to be done on a few values to find an exact URL match.
const char* const kIndexEntriesRequest =
"CREATE INDEX entries_request_match_index "
"ON entries (cache_id, request_url_no_query_hash, "
"request_url_query_hash)";
const char* const kTableRequestHeaders =
"CREATE TABLE request_headers ("
"name TEXT NOT NULL, "
"value TEXT NOT NULL, "
"entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
")";
const char* const kTableResponseHeaders =
"CREATE TABLE response_headers ("
"name TEXT NOT NULL, "
"value TEXT NOT NULL, "
"entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
")";
// We need an index on response_headers, but not on request_headers,
// because we quickly need to determine if a VARY header is present.
const char* const kIndexResponseHeadersName =
"CREATE INDEX response_headers_name_index "
"ON response_headers (name)";
// NOTE: key allows NULL below since that is how "" is represented
// in a BLOB column. We use BLOB to avoid encoding issues
// with storing DOMStrings.
const char* const kTableStorage =
"CREATE TABLE storage ("
"namespace INTEGER NOT NULL, "
"key BLOB NULL, "
"cache_id INTEGER NOT NULL REFERENCES caches(id), "
"PRIMARY KEY(namespace, key) "
")";
// ---------
// End schema definition
// ---------
const int32_t kMaxEntriesPerStatement = 255;
const uint32_t kPageSize = 4 * 1024;
@ -87,12 +212,18 @@ static_assert(int(RequestCache::Default) == 0 &&
int(RequestCache::Only_if_cached) == 5 &&
int(RequestCache::EndGuard_) == 6,
"RequestCache values are as expected");
static_assert(int(RequestRedirect::Follow) == 0 &&
int(RequestRedirect::Error) == 1 &&
int(RequestRedirect::Manual) == 2 &&
int(RequestRedirect::EndGuard_) == 3,
"RequestRedirect values are as expected");
static_assert(int(ResponseType::Basic) == 0 &&
int(ResponseType::Cors) == 1 &&
int(ResponseType::Default) == 2 &&
int(ResponseType::Error) == 3 &&
int(ResponseType::Opaque) == 4 &&
int(ResponseType::EndGuard_) == 5,
int(ResponseType::Opaqueredirect) == 5 &&
int(ResponseType::EndGuard_) == 6,
"ResponseType values are as expected");
// If the static_asserts below fails, it means that you have changed the
@ -206,10 +337,12 @@ static nsresult CreateAndBindKeyStatement(mozIStorageConnection* aConn,
mozIStorageStatement** aStateOut);
static nsresult HashCString(nsICryptoHash* aCrypto, const nsACString& aIn,
nsACString& aOut);
nsresult Validate(mozIStorageConnection* aConn);
nsresult Migrate(mozIStorageConnection* aConn);
} // namespace
nsresult
CreateSchema(mozIStorageConnection* aConn)
CreateOrMigrateSchema(mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
@ -219,135 +352,56 @@ CreateSchema(mozIStorageConnection* aConn)
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (schemaVersion == kLatestSchemaVersion) {
// We already have the correct schema, so just get started.
// We already have the correct schema version. Validate it matches
// our expected schema and then proceed.
rv = Validate(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
if (!schemaVersion) {
// The caches table is the single source of truth about what Cache
// objects exist for the origin. The contents of the Cache are stored
// in the entries table that references back to caches.
//
// The caches table is also referenced from storage. Rows in storage
// represent named Cache objects. There are cases, however, where
// a Cache can still exist, but not be in a named Storage. For example,
// when content is still using the Cache after CacheStorage::Delete()
// has been run.
//
// For now, the caches table mainly exists for data integrity with
// foreign keys, but could be expanded to contain additional cache object
// information.
//
// AUTOINCREMENT is necessary to prevent CacheId values from being reused.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE caches ("
"id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT "
");"
));
mozStorageTransaction trans(aConn, false,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
bool needVacuum = false;
if (schemaVersion) {
// A schema exists, but its not the current version. Attempt to
// migrate it to our new schema.
rv = Migrate(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Security blobs are quite large and duplicated for every Response from
// the same https origin. This table is used to de-duplicate this data.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE security_info ("
"id INTEGER NOT NULL PRIMARY KEY, "
"hash BLOB NOT NULL, " // first 8-bytes of the sha1 hash of data column
"data BLOB NOT NULL, " // full security info data, usually a few KB
"refcount INTEGER NOT NULL"
");"
));
// Migrations happen infrequently and reflect a chance in DB structure.
// This is a good time to rebuild the database. It also helps catch
// if a new migration is incorrect by fast failing on the corruption.
needVacuum = true;
} else {
// There is no schema installed. Create the database from scratch.
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableCaches));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Index the smaller hash value instead of the large security data blob.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX security_info_hash_index ON security_info (hash);"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableSecurityInfo));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE entries ("
"id INTEGER NOT NULL PRIMARY KEY, "
"request_method TEXT NOT NULL, "
"request_url_no_query TEXT NOT NULL, "
"request_url_no_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash
"request_url_query TEXT NOT NULL, "
"request_url_query_hash BLOB NOT NULL, " // first 8-bytes of sha1 hash
"request_referrer TEXT NOT NULL, "
"request_headers_guard INTEGER NOT NULL, "
"request_mode INTEGER NOT NULL, "
"request_credentials INTEGER NOT NULL, "
"request_contentpolicytype INTEGER NOT NULL, "
"request_cache INTEGER NOT NULL, "
"request_body_id TEXT NULL, "
"response_type INTEGER NOT NULL, "
"response_url TEXT NOT NULL, "
"response_status INTEGER NOT NULL, "
"response_status_text TEXT NOT NULL, "
"response_headers_guard INTEGER NOT NULL, "
"response_body_id TEXT NULL, "
"response_security_info_id INTEGER NULL REFERENCES security_info(id), "
"response_principal_info TEXT NOT NULL, "
"response_redirected INTEGER NOT NULL, "
// Note that response_redirected_url is either going to be empty, or
// it's going to be a URL different than response_url.
"response_redirected_url TEXT NOT NULL, "
"cache_id INTEGER NOT NULL REFERENCES caches(id) ON DELETE CASCADE"
");"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexSecurityInfoHash));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Create an index to support the QueryCache() matching algorithm. This
// needs to quickly find entries in a given Cache that match the request
// URL. The url query is separated in order to support the ignoreSearch
// option. Finally, we index hashes of the URL values instead of the
// actual strings to avoid excessive disk bloat. The index will duplicate
// the contents of the columsn in the index. The hash index will prune
// the vast majority of values from the query result so that normal
// scanning only has to be done on a few values to find an exact URL match.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX entries_request_match_index "
"ON entries (cache_id, request_url_no_query_hash, "
"request_url_query_hash);"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableEntries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE request_headers ("
"name TEXT NOT NULL, "
"value TEXT NOT NULL, "
"entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
");"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexEntriesRequest));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE response_headers ("
"name TEXT NOT NULL, "
"value TEXT NOT NULL, "
"entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE"
");"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableRequestHeaders));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// We need an index on response_headers, but not on request_headers,
// because we quickly need to determine if a VARY header is present.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE INDEX response_headers_name_index "
"ON response_headers (name);"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableResponseHeaders));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// NOTE: key allows NULL below since that is how "" is represented
// in a BLOB column. We use BLOB to avoid encoding issues
// with storing DOMStrings.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"CREATE TABLE storage ("
"namespace INTEGER NOT NULL, "
"key BLOB NULL, "
"cache_id INTEGER NOT NULL REFERENCES caches(id), "
"PRIMARY KEY(namespace, key) "
");"
));
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kIndexResponseHeadersName));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(nsDependentCString(kTableStorage));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->SetSchemaVersion(kLatestSchemaVersion);
@ -357,8 +411,16 @@ CreateSchema(mozIStorageConnection* aConn)
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
if (schemaVersion != kLatestSchemaVersion) {
return NS_ERROR_FAILURE;
rv = Validate(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = trans.Commit();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (needVacuum) {
// Unfortunately, this must be performed outside of the transaction.
aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("VACUUM"));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
return rv;
@ -1530,6 +1592,7 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
"request_credentials, "
"request_contentpolicytype, "
"request_cache, "
"request_redirect, "
"request_body_id, "
"response_type, "
"response_url, "
@ -1554,6 +1617,7 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
":request_credentials, "
":request_contentpolicytype, "
":request_cache, "
":request_redirect, "
":request_body_id, "
":response_type, "
":response_url, "
@ -1622,6 +1686,9 @@ InsertEntry(mozIStorageConnection* aConn, CacheId aCacheId,
static_cast<int32_t>(aRequest.requestCache()));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindInt32ByName(NS_LITERAL_CSTRING("request_redirect"),
static_cast<int32_t>(aRequest.requestRedirect()));
rv = BindId(state, NS_LITERAL_CSTRING("request_body_id"), aRequestBodyId);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
@ -1901,6 +1968,7 @@ ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
"request_credentials, "
"request_contentpolicytype, "
"request_cache, "
"request_redirect, "
"request_body_id "
"FROM entries "
"WHERE id=:id;"
@ -1955,13 +2023,19 @@ ReadRequest(mozIStorageConnection* aConn, EntryId aEntryId,
aSavedRequestOut->mValue.requestCache() =
static_cast<RequestCache>(requestCache);
int32_t requestRedirect;
rv = state->GetInt32(9, &requestRedirect);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedRequestOut->mValue.requestRedirect() =
static_cast<RequestRedirect>(requestRedirect);
bool nullBody = false;
rv = state->GetIsNull(9, &nullBody);
rv = state->GetIsNull(10, &nullBody);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
aSavedRequestOut->mHasBodyId = !nullBody;
if (aSavedRequestOut->mHasBodyId) {
rv = ExtractId(state, 9, &aSavedRequestOut->mBodyId);
rv = ExtractId(state, 10, &aSavedRequestOut->mBodyId);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
@ -2185,6 +2259,225 @@ IncrementalVacuum(mozIStorageConnection* aConn)
return NS_OK;
}
namespace {
#ifdef DEBUG
struct Expect
{
// Expect exact SQL
Expect(const char* aName, const char* aType, const char* aSql)
: mName(aName)
, mType(aType)
, mSql(aSql)
, mIgnoreSql(false)
{ }
// Ignore SQL
Expect(const char* aName, const char* aType)
: mName(aName)
, mType(aType)
, mIgnoreSql(true)
{ }
const nsCString mName;
const nsCString mType;
const nsCString mSql;
const bool mIgnoreSql;
};
#endif
nsresult
Validate(mozIStorageConnection* aConn)
{
int32_t schemaVersion;
nsresult rv = aConn->GetSchemaVersion(&schemaVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
if (NS_WARN_IF(schemaVersion != kLatestSchemaVersion)) {
return NS_ERROR_FAILURE;
}
#ifdef DEBUG
// This is the schema we expect the database at the latest version to
// contain. Update this list if you add a new table or index.
Expect expect[] = {
Expect("caches", "table", kTableCaches),
Expect("sqlite_sequence", "table"), // auto-gen by sqlite
Expect("security_info", "table", kTableSecurityInfo),
Expect("security_info_hash_index", "index", kIndexSecurityInfoHash),
Expect("entries", "table", kTableEntries),
Expect("entries_request_match_index", "index", kIndexEntriesRequest),
Expect("request_headers", "table", kTableRequestHeaders),
Expect("response_headers", "table", kTableResponseHeaders),
Expect("response_headers_name_index", "index", kIndexResponseHeadersName),
Expect("storage", "table", kTableStorage),
Expect("sqlite_autoindex_storage_1", "index"), // auto-gen by sqlite
};
const uint32_t expectLength = sizeof(expect) / sizeof(Expect);
// Read the schema from the sqlite_master table and compare.
nsCOMPtr<mozIStorageStatement> state;
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT name, type, sql FROM sqlite_master;"
), getter_AddRefs(state));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool hasMoreData = false;
while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
nsAutoCString name;
rv = state->GetUTF8String(0, name);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsAutoCString type;
rv = state->GetUTF8String(1, type);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsAutoCString sql;
rv = state->GetUTF8String(2, sql);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
bool foundMatch = false;
for (uint32_t i = 0; i < expectLength; ++i) {
if (name == expect[i].mName) {
if (type != expect[i].mType) {
NS_WARNING(nsPrintfCString("Unexpected type for Cache schema entry %s",
name.get()).get());
return NS_ERROR_FAILURE;
}
if (!expect[i].mIgnoreSql && sql != expect[i].mSql) {
NS_WARNING(nsPrintfCString("Unexpected SQL for Cache schema entry %s",
name.get()).get());
return NS_ERROR_FAILURE;
}
foundMatch = true;
break;
}
}
if (NS_WARN_IF(!foundMatch)) {
NS_WARNING(nsPrintfCString("Unexpected schema entry %s in Cache database",
name.get()).get());
return NS_ERROR_FAILURE;
}
}
#endif
return rv;
}
// -----
// Schema migration code
// -----
typedef nsresult (*MigrationFunc)(mozIStorageConnection*);
struct Migration
{
Migration(int32_t aFromVersion, MigrationFunc aFunc)
: mFromVersion(aFromVersion)
, mFunc(aFunc)
{ }
int32_t mFromVersion;
MigrationFunc mFunc;
};
// Declare migration functions here. Each function should upgrade
// the version by a single increment. Don't skip versions.
nsresult MigrateFrom15To16(mozIStorageConnection* aConn);
// Configure migration functions to run for the given starting version.
Migration sMigrationList[] = {
Migration(15, MigrateFrom15To16),
};
uint32_t sMigrationListLength = sizeof(sMigrationList) / sizeof(Migration);
nsresult
Migrate(mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
int32_t currentVersion = 0;
nsresult rv = aConn->GetSchemaVersion(&currentVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
while (currentVersion < kLatestSchemaVersion) {
// Wiping old databases is handled in DBAction because it requires
// making a whole new mozIStorageConnection. Make sure we don't
// accidentally get here for one of those old databases.
MOZ_ASSERT(currentVersion >= kFirstShippedSchemaVersion);
for (uint32_t i = 0; i < sMigrationListLength; ++i) {
if (sMigrationList[i].mFromVersion == currentVersion) {
rv = sMigrationList[i].mFunc(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
break;
}
}
DebugOnly<int32_t> lastVersion = currentVersion;
rv = aConn->GetSchemaVersion(&currentVersion);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
MOZ_ASSERT(currentVersion > lastVersion);
}
MOZ_ASSERT(currentVersion == kLatestSchemaVersion);
return rv;
}
nsresult MigrateFrom15To16(mozIStorageConnection* aConn)
{
MOZ_ASSERT(!NS_IsMainThread());
MOZ_ASSERT(aConn);
// Add the request_redirect column with a default value of "follow". Note,
// we only use a default value here because its required by ALTER TABLE and
// we need to apply the default "follow" to existing records in the table.
// We don't actually want to keep the default in the schema for future
// INSERTs.
nsresult rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE entries "
"ADD COLUMN request_redirect INTEGER NOT NULL DEFAULT 0"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// Now overwrite the master SQL for the entries table to remove the column
// default value. This is also necessary for our Validate() method to
// pass on this database.
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"PRAGMA writable_schema = ON"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCOMPtr<mozIStorageStatement> state;
rv = aConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE sqlite_master SET sql=:sql WHERE name='entries'"
), getter_AddRefs(state));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->BindUTF8StringByName(NS_LITERAL_CSTRING("sql"),
nsDependentCString(kTableEntries));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = state->Execute();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->SetSchemaVersion(16);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = aConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"PRAGMA writable_schema = OFF"
));
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
return rv;
}
} // anonymous namespace
} // namespace db
} // namespace cache
} // namespace dom

View File

@ -29,8 +29,9 @@ struct SavedResponse;
namespace db {
// Note, this cannot be executed within a transaction.
nsresult
CreateSchema(mozIStorageConnection* aConn);
CreateOrMigrateSchema(mozIStorageConnection* aConn);
// Note, this cannot be executed within a transaction.
nsresult
@ -116,8 +117,9 @@ StorageGetKeys(mozIStorageConnection* aConn, Namespace aNamespace,
nsresult
IncrementalVacuum(mozIStorageConnection* aConn);
// We will wipe out databases with a schema versions less than this.
extern const int32_t kMaxWipeSchemaVersion;
// We will wipe out databases with a schema versions less than this. Newer
// versions will be migrated on open to the latest schema version.
extern const int32_t kFirstShippedSchemaVersion;
} // namespace db
} // namespace cache

View File

@ -39,6 +39,11 @@ namespace IPC {
mozilla::dom::RequestCache::Default,
mozilla::dom::RequestCache::EndGuard_> {};
template<>
struct ParamTraits<mozilla::dom::RequestRedirect> :
public ContiguousEnumSerializer<mozilla::dom::RequestRedirect,
mozilla::dom::RequestRedirect::Follow,
mozilla::dom::RequestRedirect::EndGuard_> {};
template<>
struct ParamTraits<mozilla::dom::ResponseType> :
public ContiguousEnumSerializer<mozilla::dom::ResponseType,
mozilla::dom::ResponseType::Basic,

15
dom/cache/Manager.cpp vendored
View File

@ -54,16 +54,9 @@ public:
nsresult rv = BodyCreateDir(aDBDir);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
{
mozStorageTransaction trans(aConn, false,
mozIStorageConnection::TRANSACTION_IMMEDIATE);
rv = db::CreateSchema(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
rv = trans.Commit();
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
}
// executes in its own transaction
rv = db::CreateOrMigrateSchema(aConn);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
// If the Context marker file exists, then the last session was
// not cleanly shutdown. In these cases sqlite will ensure that
@ -400,7 +393,7 @@ private:
while (iter.HasMore()) {
nsRefPtr<Manager> manager = iter.GetNext();
if (aOrigin.IsVoid() ||
manager->mManagerId->ExtendedOrigin() == aOrigin) {
manager->mManagerId->QuotaOrigin() == aOrigin) {
manager->Abort();
}
}

View File

@ -21,26 +21,16 @@ ManagerId::Create(nsIPrincipal* aPrincipal, ManagerId** aManagerIdOut)
MOZ_ASSERT(NS_IsMainThread());
// The QuotaManager::GetInfoFromPrincipal() has special logic for system
// and about: principals. We currently don't need the system principal logic
// because ManagerId only uses the origin for in memory comparisons. We
// also don't do any special logic to host the same Cache for different about:
// pages, so we don't need those checks either.
//
// But, if we get the same QuotaManager directory for different about:
// origins, we probably only want one Manager instance. So, we might
// want to start using the QM's concept of origin uniqueness here.
//
// TODO: consider using QuotaManager's modified origin here (bug 1112071)
nsCString origin;
nsresult rv = aPrincipal->GetOriginNoSuffix(origin);
// and about: principals. We need to use the same modified origin in
// order to interpret calls from QM correctly.
nsCString quotaOrigin;
nsresult rv = QuotaManager::GetInfoFromPrincipal(aPrincipal,
nullptr, //group
&quotaOrigin,
nullptr); // is app
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsCString jarPrefix;
rv = aPrincipal->GetJarPrefix(jarPrefix);
if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, origin, jarPrefix);
nsRefPtr<ManagerId> ref = new ManagerId(aPrincipal, quotaOrigin);
ref.forget(aManagerIdOut);
return NS_OK;
@ -54,10 +44,9 @@ ManagerId::Principal() const
return ref.forget();
}
ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin,
const nsACString& aJarPrefix)
ManagerId::ManagerId(nsIPrincipal* aPrincipal, const nsACString& aQuotaOrigin)
: mPrincipal(aPrincipal)
, mExtendedOrigin(aJarPrefix + aOrigin)
, mQuotaOrigin(aQuotaOrigin)
{
MOZ_ASSERT(mPrincipal);
}

View File

@ -29,16 +29,15 @@ public:
// Main thread only
already_AddRefed<nsIPrincipal> Principal() const;
const nsACString& ExtendedOrigin() const { return mExtendedOrigin; }
const nsACString& QuotaOrigin() const { return mQuotaOrigin; }
bool operator==(const ManagerId& aOther) const
{
return mExtendedOrigin == aOther.mExtendedOrigin;
return mQuotaOrigin == aOther.mQuotaOrigin;
}
private:
ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin,
const nsACString& aJarPrefix);
ManagerId(nsIPrincipal* aPrincipal, const nsACString& aOrigin);
~ManagerId();
ManagerId(const ManagerId&) = delete;
@ -48,7 +47,7 @@ private:
nsCOMPtr<nsIPrincipal> mPrincipal;
// immutable to allow threadsfe access
const nsCString mExtendedOrigin;
const nsCString mQuotaOrigin;
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::cache::ManagerId)

View File

@ -179,6 +179,7 @@ TypeUtils::ToCacheRequest(CacheRequest& aOut, InternalRequest* aIn,
aOut.credentials() = aIn->GetCredentialsMode();
aOut.contentPolicyType() = aIn->ContentPolicyType();
aOut.requestCache() = aIn->GetCacheMode();
aOut.requestRedirect() = aIn->GetRedirectMode();
if (aBodyAction == IgnoreBody) {
aOut.body() = void_t();
@ -212,8 +213,8 @@ TypeUtils::ToCacheResponseWithoutBody(CacheResponse& aOut,
}
}
aOut.status() = aIn.GetStatus();
aOut.statusText() = aIn.GetStatusText();
aOut.status() = aIn.GetUnfilteredStatus();
aOut.statusText() = aIn.GetUnfilteredStatusText();
nsRefPtr<InternalHeaders> headers = aIn.UnfilteredHeaders();
MOZ_ASSERT(headers);
if (HasVaryStar(headers)) {
@ -245,7 +246,7 @@ TypeUtils::ToCacheResponse(CacheResponse& aOut, Response& aIn, ErrorResult& aRv)
}
nsCOMPtr<nsIInputStream> stream;
ir->GetInternalBody(getter_AddRefs(stream));
ir->GetUnfilteredBody(getter_AddRefs(stream));
if (stream) {
aIn.SetBodyUsed();
}
@ -304,17 +305,20 @@ TypeUtils::ToResponse(const CacheResponse& aIn)
switch (aIn.type())
{
case ResponseType::Default:
break;
case ResponseType::Opaque:
ir = ir->OpaqueResponse();
break;
case ResponseType::Basic:
ir = ir->BasicResponse();
break;
case ResponseType::Cors:
ir = ir->CORSResponse();
break;
case ResponseType::Default:
break;
case ResponseType::Opaque:
ir = ir->OpaqueResponse();
break;
case ResponseType::Opaqueredirect:
ir = ir->OpaqueRedirectResponse();
break;
default:
MOZ_CRASH("Unexpected ResponseType!");
}
@ -340,6 +344,7 @@ TypeUtils::ToInternalRequest(const CacheRequest& aIn)
internalRequest->SetCredentialsMode(aIn.credentials());
internalRequest->SetContentPolicyType(aIn.contentPolicyType());
internalRequest->SetCacheMode(aIn.requestCache());
internalRequest->SetRedirectMode(aIn.requestRedirect());
nsRefPtr<InternalHeaders> internalHeaders =
ToInternalHeaders(aIn.headers(), aIn.headersGuard());

4
dom/cache/moz.build vendored
View File

@ -100,3 +100,7 @@ MOCHITEST_CHROME_MANIFESTS += [
BROWSER_CHROME_MANIFESTS += [
'test/mochitest/browser.ini',
]
XPCSHELL_TESTS_MANIFESTS += [
'test/xpcshell/xpcshell.ini',
]

77
dom/cache/test/xpcshell/head.js vendored Normal file
View File

@ -0,0 +1,77 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*
* All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
* and are CC licensed by https://www.flickr.com/photos/legofenris/.
*/
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
// services required be initialized in order to run CacheStorage
var ss = Cc['@mozilla.org/storage/service;1']
.createInstance(Ci.mozIStorageService);
var sts = Cc['@mozilla.org/network/stream-transport-service;1']
.getService(Ci.nsIStreamTransportService);
var hash = Cc['@mozilla.org/security/hash;1']
.createInstance(Ci.nsICryptoHash);
// Expose Cache and Fetch symbols on the global
Cu.importGlobalProperties(['caches', 'fetch']);
// Extract a zip file into the profile
function create_test_profile(zipFileName) {
do_get_profile();
var directoryService = Cc['@mozilla.org/file/directory_service;1']
.getService(Ci.nsIProperties);
var profileDir = directoryService.get('ProfD', Ci.nsIFile);
var currentDir = directoryService.get('CurWorkD', Ci.nsIFile);
var packageFile = currentDir.clone();
packageFile.append(zipFileName);
var zipReader = Cc['@mozilla.org/libjar/zip-reader;1']
.createInstance(Ci.nsIZipReader);
zipReader.open(packageFile);
var entryNames = [];
var entries = zipReader.findEntries(null);
while (entries.hasMore()) {
var entry = entries.getNext();
entryNames.push(entry);
}
entryNames.sort();
for (var entryName of entryNames) {
var zipentry = zipReader.getEntry(entryName);
var file = profileDir.clone();
entryName.split('/').forEach(function(part) {
file.append(part);
});
if (zipentry.isDirectory) {
file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt('0755', 8));
} else {
var istream = zipReader.getInputStream(entryName);
var ostream = Cc['@mozilla.org/network/file-output-stream;1']
.createInstance(Ci.nsIFileOutputStream);
ostream.init(file, -1, parseInt('0644', 8), 0);
var bostream = Cc['@mozilla.org/network/buffered-output-stream;1']
.createInstance(Ci.nsIBufferedOutputStream);
bostream.init(ostream, 32 * 1024);
bostream.writeFrom(istream, istream.available());
istream.close();
bostream.close();
}
}
zipReader.close();
}

142
dom/cache/test/xpcshell/make_profile.js vendored Normal file
View File

@ -0,0 +1,142 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*
* All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
* and are CC licensed by https://www.flickr.com/photos/legofenris/.
*/
var Cc = Components.classes;
var Ci = Components.interfaces;
var Cu = Components.utils;
// Enumerate the directory tree and store results in entryList as
//
// { path: 'a/b/c', file: <nsIFile> }
//
// The algorithm starts with the first entry already in entryList.
function enumerate_tree(entryList) {
for (var index = 0; index < entryList.length; ++index) {
var path = entryList[index].path;
var file = entryList[index].file;
if (file.isDirectory()) {
var dirList = file.directoryEntries;
while (dirList.hasMoreElements()) {
var dirFile = dirList.getNext().QueryInterface(Ci.nsIFile);
entryList.push({ path: path + '/' + dirFile.leafName, file: dirFile });
}
}
}
}
function zip_profile(zipFile, profileDir) {
var zipWriter = Cc['@mozilla.org/zipwriter;1']
.createInstance(Ci.nsIZipWriter);
zipWriter.open(zipFile, 0x04 | 0x08 | 0x20);
var root = profileDir.clone();
root.append('storage');
root.append('default');
root.append('chrome');
var entryList = [{path: 'storage/default/chrome', file: root}];
enumerate_tree(entryList);
entryList.forEach(function(entry) {
if (entry.file.isDirectory()) {
zipWriter.addEntryDirectory(entry.path, entry.file.lastModifiedTime,
false);
} else {
var istream = Cc['@mozilla.org/network/file-input-stream;1']
.createInstance(Ci.nsIFileInputStream);
istream.init(entry.file, -1, -1, 0);
zipWriter.addEntryStream(entry.path, entry.file.lastModifiedTime,
Ci.nsIZipWriter.COMPRESSION_DEFAULT, istream,
false);
istream.close();
}
});
zipWriter.close();
}
function exactGC() {
return new Promise(function(resolve) {
var count = 0;
function doPreciseGCandCC() {
function scheduleGCCallback() {
Cu.forceCC();
if (++count < 2) {
doPreciseGCandCC();
} else {
resolve();
}
}
Cu.schedulePreciseGC(scheduleGCCallback);
}
doPreciseGCandCC();
});
}
function resetQuotaManager() {
return new Promise(function(resolve) {
var qm = Cc['@mozilla.org/dom/quota/manager;1']
.getService(Ci.nsIQuotaManager);
var prefService = Cc['@mozilla.org/preferences-service;1']
.getService(Ci.nsIPrefService);
// enable quota manager testing mode
var pref = 'dom.quotaManager.testing';
prefService.getBranch(null).setBoolPref(pref, true);
qm.reset();
// disable quota manager testing mode
//prefService.getBranch(null).setBoolPref(pref, false);
var uri = Cc['@mozilla.org/network/io-service;1']
.getService(Ci.nsIIOService)
.newURI('http://example.com', null, null);
var principal = Cc['@mozilla.org/scriptsecuritymanager;1']
.getService(Ci.nsIScriptSecurityManager)
.getSystemPrincipal();
// use getUsageForPrincipal() to get a callback when the reset() is done
qm.getUsageForPrincipal(principal, function(principal, usage, fileUsage) {
resolve(usage);
});
});
}
function run_test() {
do_test_pending();
do_get_profile();
var directoryService = Cc['@mozilla.org/file/directory_service;1']
.getService(Ci.nsIProperties);
var profileDir = directoryService.get('ProfD', Ci.nsIFile);
var currentDir = directoryService.get('CurWorkD', Ci.nsIFile);
var zipFile = currentDir.clone();
zipFile.append('new_profile.zip');
if (zipFile.exists()) {
zipFile.remove(false);
}
ok(!zipFile.exists());
caches.open('xpcshell-test').then(function(c) {
var request = new Request('http://example.com/index.html');
var response = new Response('hello world');
return c.put(request, response);
}).then(exactGC).then(resetQuotaManager).then(function() {
zip_profile(zipFile, profileDir);
dump('### ### created zip at: ' + zipFile.path + '\n');
do_test_finished();
}).catch(function(e) {
do_test_finished();
ok(false, e);
});
}

Binary file not shown.

View File

@ -0,0 +1,38 @@
/**
* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*
* All images in schema_15_profile.zip are from https://github.com/mdn/sw-test/
* and are CC licensed by https://www.flickr.com/photos/legofenris/.
*/
function run_test() {
do_test_pending();
create_test_profile('schema_15_profile.zip');
var cache;
caches.open('xpcshell-test').then(function(c) {
cache = c;
ok(cache, 'cache exists');
return cache.keys();
}).then(function(requestList) {
ok(requestList.length > 0, 'should have at least one request in cache');
requestList.forEach(function(request) {
ok(request, 'each request in list should be non-null');
ok(request.redirect === 'follow', 'request.redirect should default to "follow"');
});
return Promise.all(requestList.map(function(request) {
return cache.match(request);
}));
}).then(function(responseList) {
ok(responseList.length > 0, 'should have at least one response in cache');
responseList.forEach(function(response) {
ok(response, 'each request in list should be non-null');
});
}).then(function() {
do_test_finished();
}).catch(function(e) {
ok(false, 'caught exception ' + e);
do_test_finished();
});
}

16
dom/cache/test/xpcshell/xpcshell.ini vendored 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/.
[DEFAULT]
head = head.js
tail =
skip-if = toolkit == 'gonk'
support-files =
schema_15_profile.zip
# dummy test entry to generate profile zip files
[make_profile.js]
skip-if = true
[test_migration.js]

View File

@ -353,8 +353,8 @@ EventListenerManager::AddEventListenerInternal(
if (window && !aFlags.mInSystemGroup) {
window->SetHasTouchEventListeners();
}
} else if (aEventMessage >= NS_POINTER_EVENT_START &&
aEventMessage <= NS_POINTER_LOST_CAPTURE) {
} else if (aEventMessage >= ePointerEventFirst &&
aEventMessage <= ePointerEventLast) {
nsPIDOMWindow* window = GetInnerWindowForTarget();
if (aTypeAtom == nsGkAtoms::onpointerenter ||
aTypeAtom == nsGkAtoms::onpointerleave) {

View File

@ -169,7 +169,7 @@ EVENT(click,
EventNameType_All,
eMouseEventClass)
EVENT(contextmenu,
NS_CONTEXTMENU,
eContextMenu,
EventNameType_HTMLXUL,
eMouseEventClass)
// Not supported yet
@ -311,43 +311,43 @@ EVENT(mozpointerlockerror,
EventNameType_HTML,
eBasicEventClass)
EVENT(pointerdown,
NS_POINTER_DOWN,
ePointerDown,
EventNameType_All,
ePointerEventClass)
EVENT(pointermove,
NS_POINTER_MOVE,
ePointerMove,
EventNameType_All,
ePointerEventClass)
EVENT(pointerup,
NS_POINTER_UP,
ePointerUp,
EventNameType_All,
ePointerEventClass)
EVENT(pointercancel,
NS_POINTER_CANCEL,
ePointerCancel,
EventNameType_All,
ePointerEventClass)
EVENT(pointerover,
NS_POINTER_OVER,
ePointerOver,
EventNameType_All,
ePointerEventClass)
EVENT(pointerout,
NS_POINTER_OUT,
ePointerOut,
EventNameType_All,
ePointerEventClass)
EVENT(pointerenter,
NS_POINTER_ENTER,
ePointerEnter,
EventNameType_All,
ePointerEventClass)
EVENT(pointerleave,
NS_POINTER_LEAVE,
ePointerLeave,
EventNameType_All,
ePointerEventClass)
EVENT(gotpointercapture,
NS_POINTER_GOT_CAPTURE,
ePointerGotCapture,
EventNameType_All,
ePointerEventClass)
EVENT(lostpointercapture,
NS_POINTER_LOST_CAPTURE,
ePointerLostCapture,
EventNameType_All,
ePointerEventClass)

View File

@ -543,7 +543,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
*aStatus = nsEventStatus_eIgnore;
switch (aEvent->mMessage) {
case NS_CONTEXTMENU:
case eContextMenu:
if (sIsPointerLocked) {
return NS_ERROR_DOM_INVALID_STATE_ERR;
}
@ -622,7 +622,7 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
} else {
if (sPointerEventEnabled) {
// We should synthetize corresponding pointer events
GeneratePointerEnterExit(NS_POINTER_LEAVE, mouseEvent);
GeneratePointerEnterExit(ePointerLeave, mouseEvent);
}
GenerateMouseEnterExit(mouseEvent);
//This is a window level mouse exit event and should stop here
@ -630,8 +630,8 @@ EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
break;
}
case eMouseMove:
case NS_POINTER_DOWN:
case NS_POINTER_MOVE: {
case ePointerDown:
case ePointerMove: {
// on the Mac, GenerateDragGesture() may not return until the drag
// has completed and so |aTargetFrame| may have been deleted (moving
// a bookmark, for example). If this is the case, however, we know
@ -1174,7 +1174,7 @@ CrossProcessSafeEvent(const WidgetEvent& aEvent)
case eMouseDown:
case eMouseUp:
case eMouseMove:
case NS_CONTEXTMENU:
case eContextMenu:
case eMouseEnterIntoWidget:
case eMouseExitFromWidget:
return true;
@ -1452,7 +1452,7 @@ EventStateManager::FireContextClick()
if (allowedToDispatch) {
// init the event while mCurrentTarget is still good
WidgetMouseEvent event(true, NS_CONTEXTMENU, targetWidget,
WidgetMouseEvent event(true, eContextMenu, targetWidget,
WidgetMouseEvent::eReal);
event.clickCount = 1;
FillInEventFromGestureDown(&event);
@ -1478,7 +1478,7 @@ EventStateManager::FireContextClick()
nullptr, &status);
// We don't need to dispatch to frame handling because no frames
// watch NS_CONTEXTMENU except for nsMenuFrame and that's only for
// watch eContextMenu except for nsMenuFrame and that's only for
// dismissal. That's just as well since we don't really know
// which frame to send it to.
}
@ -3001,14 +3001,14 @@ EventStateManager::PostHandleEvent(nsPresContext* aPresContext,
SetActiveManager(this, activeContent);
}
break;
case NS_POINTER_CANCEL: {
case ePointerCancel: {
if(WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent()) {
GenerateMouseEnterExit(mouseEvent);
}
// This break was commented specially
// break;
}
case NS_POINTER_UP: {
case ePointerUp: {
WidgetPointerEvent* pointerEvent = aEvent->AsPointerEvent();
// After UP/Cancel Touch pointers become invalid so we can remove relevant helper from Table
// Mouse/Pen pointers are valid all the time (not only between down/up)
@ -3872,8 +3872,7 @@ public:
~EnterLeaveDispatcher()
{
if (mEventMessage == eMouseEnter ||
mEventMessage == NS_POINTER_ENTER) {
if (mEventMessage == eMouseEnter || mEventMessage == ePointerEnter) {
for (int32_t i = mTargets.Count() - 1; i >= 0; --i) {
mESM->DispatchMouseOrPointerEvent(mMouseEvent, mEventMessage,
mTargets[i], mRelatedTarget);
@ -3944,17 +3943,17 @@ EventStateManager::NotifyMouseOut(WidgetMouseEvent* aMouseEvent,
}
// In case we go out from capturing element (retargetedByPointerCapture is true)
// we should dispatch NS_POINTER_LEAVE event and only for capturing element.
// we should dispatch ePointerLeave event and only for capturing element.
nsRefPtr<nsIContent> movingInto = aMouseEvent->retargetedByPointerCapture
? wrapper->mLastOverElement->GetParent()
: aMovingInto;
EnterLeaveDispatcher leaveDispatcher(this, wrapper->mLastOverElement,
movingInto, aMouseEvent,
isPointer ? NS_POINTER_LEAVE : eMouseLeave);
isPointer ? ePointerLeave : eMouseLeave);
// Fire mouseout
DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? NS_POINTER_OUT : eMouseOut,
DispatchMouseOrPointerEvent(aMouseEvent, isPointer ? ePointerOut : eMouseOut,
wrapper->mLastOverElement, aMovingInto);
wrapper->mLastOverFrame = nullptr;
@ -4010,7 +4009,7 @@ EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
Maybe<EnterLeaveDispatcher> enterDispatcher;
if (dispatch) {
enterDispatcher.emplace(this, aContent, lastOverElement, aMouseEvent,
isPointer ? NS_POINTER_ENTER : eMouseEnter);
isPointer ? ePointerEnter : eMouseEnter);
}
NotifyMouseOut(aMouseEvent, aContent);
@ -4027,7 +4026,7 @@ EventStateManager::NotifyMouseOver(WidgetMouseEvent* aMouseEvent,
// Fire mouseover
wrapper->mLastOverFrame =
DispatchMouseOrPointerEvent(aMouseEvent,
isPointer ? NS_POINTER_OVER : eMouseOver,
isPointer ? ePointerOver : eMouseOver,
aContent, lastOverElement);
wrapper->mLastOverElement = aContent;
} else {
@ -4149,8 +4148,8 @@ EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent)
sLastRefPoint = aMouseEvent->refPoint;
}
case NS_POINTER_MOVE:
case NS_POINTER_DOWN:
case ePointerMove:
case ePointerDown:
{
// Get the target content target (mousemove target == mouseover target)
nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
@ -4165,7 +4164,7 @@ EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent)
}
}
break;
case NS_POINTER_UP:
case ePointerUp:
{
// Get the target content target (mousemove target == mouseover target)
nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
@ -4184,8 +4183,8 @@ EventStateManager::GenerateMouseEnterExit(WidgetMouseEvent* aMouseEvent)
}
}
break;
case NS_POINTER_LEAVE:
case NS_POINTER_CANCEL:
case ePointerLeave:
case ePointerCancel:
case eMouseExitFromWidget:
{
// This is actually the window mouse exit or pointer leave event. We're not moving

View File

@ -214,7 +214,7 @@ WheelTransaction::OnEvent(WidgetEvent* aEvent)
case eMouseDown:
case eMouseDoubleClick:
case eMouseClick:
case NS_CONTEXTMENU:
case eContextMenu:
case NS_DRAGDROP_DROP:
EndTransaction();
return;

View File

@ -6,6 +6,7 @@
#include "mozilla/dom/ChannelInfo.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
#include "nsIChannel.h"
#include "nsIDocument.h"
#include "nsIHttpChannel.h"
@ -68,6 +69,20 @@ ChannelInfo::InitFromChannel(nsIChannel* aChannel)
mInited = true;
}
void
ChannelInfo::InitFromChromeGlobal(nsIGlobalObject* aGlobal)
{
MOZ_ASSERT(!mInited, "Cannot initialize the object twice");
MOZ_ASSERT(aGlobal);
MOZ_RELEASE_ASSERT(
nsContentUtils::IsSystemPrincipal(aGlobal->PrincipalOrNull()));
mSecurityInfo.Truncate();
mRedirected = false;
mInited = true;
}
void
ChannelInfo::InitFromIPCChannelInfo(const ipc::IPCChannelInfo& aChannelInfo)
{

View File

@ -12,6 +12,7 @@
class nsIChannel;
class nsIDocument;
class nsIGlobalObject;
class nsIURI;
namespace mozilla {
@ -69,6 +70,7 @@ public:
void InitFromDocument(nsIDocument* aDoc);
void InitFromChannel(nsIChannel* aChannel);
void InitFromChromeGlobal(nsIGlobalObject* aGlobal);
void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo);
// This restores every possible information stored from a previous channel

View File

@ -31,6 +31,7 @@
#include "mozilla/dom/File.h"
#include "mozilla/dom/workers/Workers.h"
#include "mozilla/unused.h"
#include "Fetch.h"
#include "InternalRequest.h"
@ -49,6 +50,7 @@ FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
, mLoadGroup(aLoadGroup)
, mRequest(aRequest)
, mFetchRecursionCount(0)
, mCORSFlagEverSet(false)
, mResponseAvailableCalled(false)
{
}
@ -94,8 +96,8 @@ FetchDriver::Fetch(bool aCORSFlag)
MOZ_CRASH("Synchronous fetch not supported");
}
nsresult
FetchDriver::ContinueFetch(bool aCORSFlag)
FetchDriver::MainFetchOp
FetchDriver::SetTaintingAndGetNextOp(bool aCORSFlag)
{
workers::AssertIsOnMainThread();
@ -105,7 +107,7 @@ FetchDriver::ContinueFetch(bool aCORSFlag)
nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url,
nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
return MainFetchOp(NETWORK_ERROR);
}
// CSP/mixed content checks.
@ -123,51 +125,91 @@ FetchDriver::ContinueFetch(bool aCORSFlag)
nsContentUtils::GetSecurityManager());
if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) {
// Disallowed by content policy.
return FailWithNetworkError();
return MainFetchOp(NETWORK_ERROR);
}
// Begin Step 4 of the Fetch algorithm
// Begin Step 8 of the Main Fetch algorithm
// https://fetch.spec.whatwg.org/#fetching
nsAutoCString scheme;
rv = requestURI->GetScheme(scheme);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
return MainFetchOp(NETWORK_ERROR);
}
rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, false /* allowIfInheritsPrincipal */);
// request's current url's origin is request's origin and the CORS flag is unset
// request's current url's scheme is "data" and request's same-origin data-URL flag is set
// request's current url's scheme is "about"
rv = mPrincipal->CheckMayLoad(requestURI, false /* report */,
false /* allowIfInheritsPrincipal */);
if ((!aCORSFlag && NS_SUCCEEDED(rv)) ||
(scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) ||
scheme.EqualsLiteral("about")) {
return BasicFetch();
return MainFetchOp(BASIC_FETCH);
}
// request's mode is "same-origin"
if (mRequest->Mode() == RequestMode::Same_origin) {
return FailWithNetworkError();
return MainFetchOp(NETWORK_ERROR);
}
// request's mode is "no-cors"
if (mRequest->Mode() == RequestMode::No_cors) {
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE);
return BasicFetch();
return MainFetchOp(BASIC_FETCH);
}
// request's current url's scheme is not one of "http" and "https"
if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) {
return MainFetchOp(NETWORK_ERROR);
}
// request's mode is "cors-with-forced-preflight"
// request's unsafe-request flag is set and either request's method is not
// a simple method or a header in request's header list is not a simple header
if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
(mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() ||
!mRequest->Headers()->HasOnlySimpleHeaders()))) {
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
mRequest->SetRedirectMode(RequestRedirect::Error);
// Note, the following text from Main Fetch step 8 is handled in
// nsCORSListenerProxy when CheckRequestApproved() fails:
//
// The result of performing an HTTP fetch using request with the CORS
// flag and CORS-preflight flag set. If the result is a network error,
// clear cache entries using request.
return MainFetchOp(HTTP_FETCH, true /* cors */, true /* preflight */);
}
// Otherwise
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
return MainFetchOp(HTTP_FETCH, true /* cors */, false /* preflight */);
}
nsresult
FetchDriver::ContinueFetch(bool aCORSFlag)
{
workers::AssertIsOnMainThread();
MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag);
if (nextOp.mType == NETWORK_ERROR) {
return FailWithNetworkError();
}
bool corsPreflight = false;
if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight ||
(mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) {
corsPreflight = true;
if (nextOp.mType == BASIC_FETCH) {
return BasicFetch();
}
// The Request constructor should ensure that no-cors requests have simple
// method and headers, so we should never attempt to preflight for such
// Requests.
MOZ_ASSERT_IF(mRequest->Mode() == RequestMode::No_cors, !corsPreflight);
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS);
return HttpFetch(true /* aCORSFlag */, corsPreflight);
}
if (nextOp.mType == HTTP_FETCH) {
return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag);
}
MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!");
return FailWithNetworkError();
}
nsresult
FetchDriver::BasicFetch()
@ -331,6 +373,11 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
mResponse = nullptr;
nsresult rv;
// We need to track the CORS flag through redirects. Since there is no way
// for us to go from CORS mode to non-CORS mode, we just need to remember
// if it has ever been set.
mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag;
nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
FailWithNetworkError();
@ -487,6 +534,13 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
// Auth may require prompting, we don't support it yet.
// The next patch in this same bug prevents this from aborting the request.
// Credentials checks for CORS are handled by nsCORSListenerProxy,
nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan);
// Conversion between enumerations is safe due to static asserts in
// dom/workers/ServiceWorkerManager.cpp
internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode()));
internalChan->SetRedirectMode(static_cast<uint32_t>(mRequest->GetRedirectMode()));
}
// Step 5. Proxy authentication will be handled by Necko.
@ -534,10 +588,10 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
nsCOMPtr<nsIStreamListener> listener = this;
// Unless the cors mode is explicitly no-cors, we set up a cors proxy even in
// the same-origin case, since the proxy does not enforce cors header checks
// in the same-origin case.
if (mRequest->Mode() != RequestMode::No_cors) {
// Only use nsCORSListenerProxy if we are in CORS mode. Otherwise it
// will overwrite the CorsMode flag unconditionally to "cors" or
// "cors-with-forced-preflight".
if (mRequest->Mode() == RequestMode::Cors) {
// Set up a CORS proxy that will handle the various requirements of the CORS
// protocol. It handles the preflight cache and CORS response headers.
// If the request is allowed, it will start our original request
@ -616,6 +670,9 @@ FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse, nsIURI* aF
case InternalRequest::RESPONSETAINT_OPAQUE:
filteredResponse = aResponse->OpaqueResponse();
break;
case InternalRequest::RESPONSETAINT_OPAQUEREDIRECT:
filteredResponse = aResponse->OpaqueRedirectResponse();
break;
default:
MOZ_CRASH("Unexpected case");
}
@ -696,8 +753,11 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
nsISupports* aContext)
{
workers::AssertIsOnMainThread();
MOZ_ASSERT(!mPipeOutputStream);
MOZ_ASSERT(mObserver);
// Note, this can be called multiple times if we are doing an opaqueredirect.
// In that case we will get a simulated OnStartRequest() and then the real
// channel will call in with an errored OnStartRequest().
nsresult rv;
aRequest->GetStatus(&rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -705,6 +765,10 @@ FetchDriver::OnStartRequest(nsIRequest* aRequest,
return rv;
}
// We should only get to the following code once.
MOZ_ASSERT(!mPipeOutputStream);
MOZ_ASSERT(mObserver);
nsRefPtr<InternalResponse> response;
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
if (httpChannel) {
@ -839,33 +903,59 @@ FetchDriver::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
nsresult rv;
// Section 4.2, Step 4.6-4.7, enforcing a redirect count is done by Necko.
// The pref used is "network.http.redirection-limit" which is set to 20 by
// default.
//
// Step 4.8. We only unset this for spec compatibility. Any actions we take
// on mRequest here do not affect what the channel does.
// HTTP Fetch step 5, "redirect status", step 1
if (NS_WARN_IF(mRequest->GetRedirectMode() == RequestRedirect::Error)) {
aOldChannel->Cancel(NS_BINDING_FAILED);
return NS_BINDING_FAILED;
}
// HTTP Fetch step 5, "redirect status", steps 2 through 6 are automatically
// handled by necko before calling AsyncOnChannelRedirect() with the new
// nsIChannel.
// HTTP Fetch step 5, "redirect status", steps 7 and 8 enforcing a redirect
// count are done by Necko. The pref used is "network.http.redirection-limit"
// which is set to 20 by default.
// HTTP Fetch Step 9, "redirect status". We only unset this for spec
// compatibility. Any actions we take on mRequest here do not affect what the
//channel does.
mRequest->UnsetSameOriginDataURL();
//
// Requests that require preflight are not permitted to redirect.
// Fetch spec section 4.2 "HTTP Fetch", step 4.9 just uses the manual
// redirect flag to decide whether to execute step 4.10 or not. We do not
// represent it in our implementation.
// The only thing we do is to check if the request requires a preflight (part
// of step 4.9), in which case we abort. This part cannot be done by
// nsCORSListenerProxy since it does not have access to mRequest.
// which case. Step 4.10.3 is handled by OnRedirectVerifyCallback(), and all
// the other steps are handled by nsCORSListenerProxy.
if (!NS_IsInternalSameURIRedirect(aOldChannel, aNewChannel, aFlags)) {
rv = DoesNotRequirePreflight(aNewChannel);
if (NS_FAILED(rv)) {
NS_WARNING("FetchDriver::OnChannelRedirect: "
"DoesNotRequirePreflight returned failure");
return rv;
}
// HTTP Fetch step 5, "redirect status", step 10 requires us to halt the
// redirect, but successfully return an opaqueredirect Response to the
// initiating Fetch.
if (mRequest->GetRedirectMode() == RequestRedirect::Manual) {
// Ideally we would simply not cancel the old channel and allow it to
// be processed as normal. Unfortunately this is quite fragile and
// other redirect handlers can easily break it for certain use cases.
//
// For example, nsCORSListenerProxy cancels vetoed redirect channels.
// The HTTP cache will also error on vetoed redirects when the
// redirect has been previously cached.
//
// Therefore simulate the completion of the channel to produce the
// opaqueredirect Response and then cancel the original channel. This
// will result in OnStartRequest() getting called twice, but the second
// time will be with an error response (from the Cancel) which will
// be ignored.
mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUEREDIRECT);
unused << OnStartRequest(aOldChannel, nullptr);
unused << OnStopRequest(aOldChannel, nullptr, NS_OK);
aOldChannel->Cancel(NS_BINDING_FAILED);
return NS_BINDING_FAILED;
}
// The following steps are from HTTP Fetch step 5, "redirect status", step 11
// which requires the RequestRedirect to be "follow".
MOZ_ASSERT(mRequest->GetRedirectMode() == RequestRedirect::Follow);
// HTTP Fetch step 5, "redirect status", steps 11.1 and 11.2 block redirecting
// to a URL with credentials in CORS mode. This is implemented in
// nsCORSListenerProxy.
mRedirectCallback = aCallback;
mOldRedirectChannel = aOldChannel;
mNewRedirectChannel = aNewChannel;
@ -958,12 +1048,12 @@ FetchDriver::GetInterface(const nsIID& aIID, void **aResult)
NS_IMETHODIMP
FetchDriver::OnRedirectVerifyCallback(nsresult aResult)
{
// On a successful redirect we perform the following substeps of Section 4.2,
// step 4.10.
// On a successful redirect we perform the following substeps of HTTP Fetch,
// step 5, "redirect status", step 11.
if (NS_SUCCEEDED(aResult)) {
// Step 4.10.3 "Set request's url to locationURL." so that when we set the
// Response's URL from the Request's URL in Section 4, step 6, we get the
// final value.
// Step 11.5 "Append locationURL to request's url list." so that when we set the
// Response's URL from the Request's URL in Main Fetch, step 15, we get the
// final value. Note, we still use a single URL value instead of a list.
nsCOMPtr<nsIURI> newURI;
nsresult rv = NS_GetFinalChannelURI(mNewRedirectChannel, getter_AddRefs(newURI));
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -980,6 +1070,22 @@ FetchDriver::OnRedirectVerifyCallback(nsresult aResult)
mOldRedirectChannel->Cancel(aResult);
}
// Implement Main Fetch step 8 again on redirect.
MainFetchOp nextOp = SetTaintingAndGetNextOp(mCORSFlagEverSet);
if (nextOp.mType == NETWORK_ERROR) {
// Cancel the channel if Main Fetch blocks the redirect from continuing.
aResult = NS_ERROR_DOM_BAD_URI;
mOldRedirectChannel->Cancel(aResult);
} else {
// Otherwise, we rely on necko and the CORS proxy to do the right thing
// as the redirect is followed. In general this means basic or http
// fetch. If we've ever been CORS, we need to stay CORS.
MOZ_ASSERT(nextOp.mType == BASIC_FETCH || nextOp.mType == HTTP_FETCH);
MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mType == HTTP_FETCH);
MOZ_ASSERT_IF(mCORSFlagEverSet, nextOp.mCORSFlag);
}
mOldRedirectChannel = nullptr;
mNewRedirectChannel = nullptr;
mRedirectCallback->OnRedirectVerifyCallback(aResult);

View File

@ -77,6 +77,7 @@ private:
nsCOMPtr<nsIChannel> mNewRedirectChannel;
nsCOMPtr<nsIDocument> mDocument;
uint32_t mFetchRecursionCount;
bool mCORSFlagEverSet;
DebugOnly<bool> mResponseAvailableCalled;
@ -85,7 +86,29 @@ private:
FetchDriver& operator=(const FetchDriver&) = delete;
~FetchDriver();
enum MainFetchOpType
{
NETWORK_ERROR,
BASIC_FETCH,
HTTP_FETCH,
NUM_MAIN_FETCH_OPS
};
struct MainFetchOp
{
explicit MainFetchOp(MainFetchOpType aType, bool aCORSFlag = false,
bool aCORSPreflightFlag = false)
: mType(aType), mCORSFlag(aCORSFlag),
mCORSPreflightFlag(aCORSPreflightFlag)
{ }
MainFetchOpType mType;
bool mCORSFlag;
bool mCORSPreflightFlag;
};
nsresult Fetch(bool aCORSFlag);
MainFetchOp SetTaintingAndGetNextOp(bool aCORSFlag);
nsresult ContinueFetch(bool aCORSFlag);
nsresult BasicFetch();
nsresult HttpFetch(bool aCORSFlag = false, bool aCORSPreflightFlag = false, bool aAuthenticationFlag = false);

View File

@ -42,6 +42,7 @@ InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult
copy->mMode = mMode;
copy->mCredentialsMode = mCredentialsMode;
copy->mCacheMode = mCacheMode;
copy->mRedirectMode = mRedirectMode;
copy->mCreatedByFetchEvent = mCreatedByFetchEvent;
return copy.forget();
}
@ -80,6 +81,7 @@ InternalRequest::InternalRequest(const InternalRequest& aOther)
, mCredentialsMode(aOther.mCredentialsMode)
, mResponseTainting(aOther.mResponseTainting)
, mCacheMode(aOther.mCacheMode)
, mRedirectMode(aOther.mRedirectMode)
, mAuthenticationFlag(aOther.mAuthenticationFlag)
, mForceOriginHeader(aOther.mForceOriginHeader)
, mPreserveContentCodings(aOther.mPreserveContentCodings)

View File

@ -92,6 +92,7 @@ public:
RESPONSETAINT_BASIC,
RESPONSETAINT_CORS,
RESPONSETAINT_OPAQUE,
RESPONSETAINT_OPAQUEREDIRECT,
};
explicit InternalRequest()
@ -102,6 +103,7 @@ public:
, mCredentialsMode(RequestCredentials::Omit)
, mResponseTainting(RESPONSETAINT_BASIC)
, mCacheMode(RequestCache::Default)
, mRedirectMode(RequestRedirect::Follow)
, mAuthenticationFlag(false)
, mForceOriginHeader(false)
, mPreserveContentCodings(false)
@ -264,6 +266,18 @@ public:
mCacheMode = aCacheMode;
}
RequestRedirect
GetRedirectMode() const
{
return mRedirectMode;
}
void
SetRedirectMode(RequestRedirect aRedirectMode)
{
mRedirectMode = aRedirectMode;
}
nsContentPolicyType
ContentPolicyType() const
{
@ -390,6 +404,7 @@ private:
RequestCredentials mCredentialsMode;
ResponseTainting mResponseTainting;
RequestCache mCacheMode;
RequestRedirect mRedirectMode;
bool mAuthenticationFlag;
bool mForceOriginHeader;

View File

@ -125,7 +125,6 @@ InternalResponse::OpaqueResponse()
nsRefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString());
response->mType = ResponseType::Opaque;
response->mTerminationReason = mTerminationReason;
response->mURL = mURL;
response->mChannelInfo = mChannelInfo;
if (mPrincipalInfo) {
response->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo);
@ -134,6 +133,16 @@ InternalResponse::OpaqueResponse()
return response.forget();
}
already_AddRefed<InternalResponse>
InternalResponse::OpaqueRedirectResponse()
{
MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response");
nsRefPtr<InternalResponse> response = OpaqueResponse();
response->mType = ResponseType::Opaqueredirect;
response->mURL = mURL;
return response.forget();
}
already_AddRefed<InternalResponse>
InternalResponse::CreateIncompleteCopy()
{

View File

@ -48,6 +48,9 @@ public:
already_AddRefed<InternalResponse>
OpaqueResponse();
already_AddRefed<InternalResponse>
OpaqueRedirectResponse();
already_AddRefed<InternalResponse>
BasicResponse();
@ -62,6 +65,7 @@ public:
MOZ_ASSERT_IF(mType == ResponseType::Basic, mWrappedResponse);
MOZ_ASSERT_IF(mType == ResponseType::Cors, mWrappedResponse);
MOZ_ASSERT_IF(mType == ResponseType::Opaque, mWrappedResponse);
MOZ_ASSERT_IF(mType == ResponseType::Opaqueredirect, mWrappedResponse);
return mType;
}
@ -91,12 +95,32 @@ public:
return mStatus;
}
uint16_t
GetUnfilteredStatus() const
{
if (mWrappedResponse) {
return mWrappedResponse->GetStatus();
}
return GetStatus();
}
const nsCString&
GetStatusText() const
{
return mStatusText;
}
const nsCString&
GetUnfilteredStatusText() const
{
if (mWrappedResponse) {
return mWrappedResponse->GetStatusText();
}
return GetStatusText();
}
InternalHeaders*
Headers()
{
@ -114,7 +138,7 @@ public:
}
void
GetInternalBody(nsIInputStream** aStream)
GetUnfilteredBody(nsIInputStream** aStream)
{
if (mWrappedResponse) {
MOZ_ASSERT(!mBody);
@ -127,12 +151,13 @@ public:
void
GetBody(nsIInputStream** aStream)
{
if (Type() == ResponseType::Opaque) {
if (Type() == ResponseType::Opaque ||
Type() == ResponseType::Opaqueredirect) {
*aStream = nullptr;
return;
}
return GetInternalBody(aStream);
return GetUnfilteredBody(aStream);
}
void

View File

@ -291,6 +291,10 @@ Request::Constructor(const GlobalObject& aGlobal,
request->SetCacheMode(cache);
}
if (aInit.mRedirect.WasPassed()) {
request->SetRedirectMode(aInit.mRedirect.Value());
}
// Request constructor step 14.
if (aInit.mMethod.WasPassed()) {
nsAutoCString method(aInit.mMethod.Value());

View File

@ -76,6 +76,12 @@ public:
return mRequest->GetCacheMode();
}
RequestRedirect
Redirect() const
{
return mRequest->GetRedirectMode();
}
RequestContext
Context() const
{

View File

@ -162,12 +162,15 @@ Response::Constructor(const GlobalObject& aGlobal,
// Grab a valid channel info from the global so this response is 'valid' for
// interception.
if (NS_IsMainThread()) {
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
MOZ_ASSERT(window);
nsIDocument* doc = window->GetExtantDoc();
MOZ_ASSERT(doc);
ChannelInfo info;
info.InitFromDocument(doc);
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(global);
if (window) {
nsIDocument* doc = window->GetExtantDoc();
MOZ_ASSERT(doc);
info.InitFromDocument(doc);
} else {
info.InitFromChromeGlobal(global);
}
internalResponse->InitChannelInfo(info);
} else {
workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate();

View File

@ -116,6 +116,34 @@ static PRLogModuleInfo* gMediaElementEventsLog;
using namespace mozilla::layers;
using mozilla::net::nsMediaFragmentURIParser;
class MOZ_STACK_CLASS AutoNotifyAudioChannelAgent
{
nsRefPtr<mozilla::dom::HTMLMediaElement> mElement;
bool mShouldNotify;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
public:
AutoNotifyAudioChannelAgent(mozilla::dom::HTMLMediaElement* aElement,
bool aNotify
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: mElement(aElement)
, mShouldNotify(aNotify)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mShouldNotify) {
mElement->NotifyAudioChannelAgent(false);
}
}
~AutoNotifyAudioChannelAgent()
{
if (mShouldNotify) {
// The audio channel agent is destroyed at this point.
if (mElement->MaybeCreateAudioChannelAgent()) {
mElement->NotifyAudioChannelAgent(true);
}
}
}
};
namespace mozilla {
namespace dom {
@ -3189,6 +3217,14 @@ void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
{
MOZ_ASSERT(NS_IsMainThread());
// If the element is gaining or losing an audio track, we need to notify
// the audio channel agent so that the correct audio-playback events will
// get dispatched.
bool audioTrackChanging = mMediaInfo.HasAudio() != aInfo->HasAudio();
AutoNotifyAudioChannelAgent autoNotify(this,
audioTrackChanging &&
mPlayingThroughTheAudioChannel);
mMediaInfo = *aInfo;
mIsEncrypted = aInfo->IsEncrypted()
#ifdef MOZ_EME
@ -4488,7 +4524,25 @@ nsresult HTMLMediaElement::UpdateChannelMuteState(float aVolume, bool aMuted)
return NS_OK;
}
void HTMLMediaElement::UpdateAudioChannelPlayingState()
bool
HTMLMediaElement::MaybeCreateAudioChannelAgent()
{
if (!mAudioChannelAgent) {
nsresult rv;
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return false;
}
MOZ_ASSERT(mAudioChannelAgent);
mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
static_cast<int32_t>(mAudioChannel),
this);
}
return true;
}
void
HTMLMediaElement::UpdateAudioChannelPlayingState()
{
bool playingThroughTheAudioChannel =
(!mPaused &&
@ -4506,18 +4560,9 @@ void HTMLMediaElement::UpdateAudioChannelPlayingState()
return;
}
if (!mAudioChannelAgent) {
nsresult rv;
mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
if (!mAudioChannelAgent) {
return;
}
mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
static_cast<int32_t>(mAudioChannel),
this);
if (MaybeCreateAudioChannelAgent()) {
NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
}
NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
}
}

View File

@ -35,10 +35,6 @@
// Define to output information on decoding and painting framerate
/* #define DEBUG_FRAME_RATE 1 */
class nsIChannel;
class nsIHttpChannel;
class nsILoadGroup;
typedef uint16_t nsMediaNetworkState;
typedef uint16_t nsMediaReadyState;
@ -56,9 +52,13 @@ class MediaTrack;
} // namespace dom
} // namespace mozilla
class AutoNotifyAudioChannelAgent;
class nsIChannel;
class nsIHttpChannel;
class nsILoadGroup;
class nsIRunnable;
class nsITimer;
class nsRange;
class nsIRunnable;
namespace mozilla {
namespace dom {
@ -78,6 +78,8 @@ class HTMLMediaElement : public nsGenericHTMLElement,
public MediaDecoderOwner,
public nsIAudioChannelAgentCallback
{
friend AutoNotifyAudioChannelAgent;
public:
typedef mozilla::TimeStamp TimeStamp;
typedef mozilla::layers::ImageContainer ImageContainer;
@ -1050,6 +1052,10 @@ protected:
// Notifies the audio channel agent when the element starts or stops playing.
void NotifyAudioChannelAgent(bool aPlaying);
// Creates the audio channel agent if needed. Returns true if the audio
// channel agent is ready to be used.
bool MaybeCreateAudioChannelAgent();
class nsAsyncEventRunner;
using nsGenericHTMLElement::DispatchEvent;
// For nsAsyncEventRunner.

View File

@ -42,6 +42,9 @@
#include "mozilla/dom/ExternalHelperAppParent.h"
#include "mozilla/dom/FileSystemRequestParent.h"
#include "mozilla/dom/GeolocationBinding.h"
#ifdef MOZ_EME
#include "mozilla/dom/MediaKeySystemAccess.h"
#endif
#include "mozilla/dom/NuwaParent.h"
#include "mozilla/dom/PContentBridgeParent.h"
#include "mozilla/dom/PContentPermissionRequestParent.h"
@ -1098,6 +1101,23 @@ ContentParent::RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
aVersion);
}
bool
ContentParent::RecvIsGMPPresentOnDisk(const nsString& aKeySystem,
const nsCString& aVersion,
bool* aIsPresent,
nsCString* aMessage)
{
#ifdef MOZ_EME
*aIsPresent = MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem,
aVersion,
*aMessage);
#else
*aIsPresent = false;
#endif
return true;
}
bool
ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
{

View File

@ -176,6 +176,10 @@ public:
nsTArray<nsCString>&& aTags,
bool* aHasPlugin,
nsCString* aVersion) override;
virtual bool RecvIsGMPPresentOnDisk(const nsString& aKeySystem,
const nsCString& aVersion,
bool* aIsPresent,
nsCString* aMessage) override;
virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override;
virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override;

View File

@ -696,6 +696,8 @@ parent:
async CreateGMPService();
sync GetGMPPluginVersionForAPI(nsCString api, nsCString[] tags)
returns (bool hasPlugin, nsCString version);
sync IsGMPPresentOnDisk(nsString keySystem, nsCString version)
returns (bool isPresent, nsCString message);
/**
* This call connects the content process to a plugin process. While this

View File

@ -173,5 +173,7 @@ BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to Fe
InterceptedErrorResponse=A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()".
InterceptedUsedResponse=A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker".
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaque", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker".
ClientRequestOpaqueInterception=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while FetchEvent.request was a client request. A client request is generally a browser navigation or top-level Worker script.
# LOCALIZATION NOTE: Do not translate "ServiceWorker", "opaqueredirect", "Response", "FetchEvent.respondWith()", or "FetchEvent.request".
BadOpaqueRedirectInterception=A ServiceWorker passed an opaqueredirect Response to FetchEvent.respondWith() while FetchEvent.request was not a navigation request.

View File

@ -77,6 +77,36 @@ GMPVideoDecoderTrialCreator::GetCreateTrialState(const nsAString& aKeySystem)
}
}
/* static */ void
GMPVideoDecoderTrialCreator::UpdateTrialCreateState(const nsAString& aKeySystem,
uint32_t aState)
{
UpdateTrialCreateState(aKeySystem, (TrialCreateState)aState);
}
/* static */ void
GMPVideoDecoderTrialCreator::UpdateTrialCreateState(const nsAString& aKeySystem,
TrialCreateState aState)
{
MOZ_ASSERT(NS_IsMainThread());
if (XRE_GetProcessType() == GeckoProcessType_Content) {
// Pref has to be set from the chrome process. Dispatch to chrome via
// GMPService.
nsCOMPtr<mozIGeckoMediaPluginService> service =
do_GetService("@mozilla.org/gecko-media-plugin-service;1");
NS_ENSURE_TRUE_VOID(service);
service->UpdateTrialCreateState(aKeySystem, (uint32_t)aState);
return;
}
const char* pref = TrialCreatePrefName(aKeySystem);
if (pref) {
Preferences::SetInt(pref, (int)aState);
}
}
void
GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(const nsAString& aKeySystem,
const nsACString& aReason)
@ -91,10 +121,8 @@ GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderFailed(const nsAString& a
return;
}
data->mStatus = Failed;
const char* pref = TrialCreatePrefName(aKeySystem);
if (pref) {
Preferences::SetInt(pref, (int)Failed);
}
UpdateTrialCreateState(aKeySystem, Failed);
for (nsRefPtr<AbstractPromiseLike>& promise: data->mPending) {
promise->Reject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, aReason);
}
@ -115,10 +143,8 @@ GMPVideoDecoderTrialCreator::TrialCreateGMPVideoDecoderSucceeded(const nsAString
return;
}
data->mStatus = Succeeded;
const char* pref = TrialCreatePrefName(aKeySystem);
if (pref) {
Preferences::SetInt(pref, (int)Succeeded);
}
UpdateTrialCreateState(aKeySystem, Succeeded);
for (nsRefPtr<AbstractPromiseLike>& promise : data->mPending) {
promise->Resolve();
}
@ -484,9 +510,9 @@ TestGMPVideoDecoder::CreateGMPVideoDecoder()
tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
UniquePtr<GetGMPVideoDecoderCallback> callback(new Callback(this));
nsCString fakeNodeId;
if (NS_FAILED(GenerateRandomName(fakeNodeId, 32)) ||
NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags, fakeNodeId, Move(callback)))) {
if (NS_FAILED(mGMPService->GetGMPVideoDecoder(&tags,
NS_LITERAL_CSTRING("fakeNodeId1234567890fakeNodeId12"),
Move(callback)))) {
ReportFailure(NS_LITERAL_CSTRING("TestGMPVideoDecoder GMPService GetGMPVideoDecoder returned failure"));
}
}
@ -498,12 +524,6 @@ GMPVideoDecoderTrialCreator::MaybeAwaitTrialCreate(const nsAString& aKeySystem,
{
MOZ_ASSERT(NS_IsMainThread());
if (XRE_GetProcessType() == GeckoProcessType_Content) {
// Currently broken with e10s...
aPromisey->Resolve();
return;
}
if (!mTestCreate.Contains(aKeySystem)) {
mTestCreate.Put(aKeySystem, new TrialCreateData(aKeySystem));
}

View File

@ -39,6 +39,8 @@ public:
MaybeAwaitTrialCreate(aKeySystem, p, aParent);
}
static void UpdateTrialCreateState(const nsAString& aKeySystem, uint32_t aState);
private:
class AbstractPromiseLike {
@ -88,6 +90,8 @@ private:
};
static TrialCreateState GetCreateTrialState(const nsAString& aKeySystem);
static void UpdateTrialCreateState(const nsAString& aKeySystem,
TrialCreateState aState);
struct TrialCreateData {
explicit TrialCreateData(const nsAString& aKeySystem)
@ -172,4 +176,4 @@ private:
} // namespace dom
} // namespace mozilla
#endif
#endif

View File

@ -4,6 +4,7 @@
* 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 "mozilla/dom/ContentChild.h"
#include "mozilla/dom/MediaKeySystemAccess.h"
#include "mozilla/dom/MediaKeySystemAccessBinding.h"
#include "mozilla/Preferences.h"
@ -110,10 +111,7 @@ static bool
AdobePluginFileExists(const nsACString& aVersionStr,
const nsAString& aFilename)
{
if (XRE_GetProcessType() != GeckoProcessType_Default) {
NS_WARNING("AdobePluginFileExists() lying because it doesn't work with e10s");
return true;
}
MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
nsCOMPtr<nsIFile> path;
nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(path));
@ -153,6 +151,61 @@ AdobePluginVoucherExists(const nsACString& aVersionStr)
}
#endif
/* static */ bool
MediaKeySystemAccess::IsGMPPresentOnDisk(const nsAString& aKeySystem,
const nsACString& aVersion,
nsACString& aOutMessage)
{
MOZ_ASSERT(NS_IsMainThread());
if (XRE_GetProcessType() != GeckoProcessType_Default) {
// We need to be able to access the filesystem, so call this in the
// main process via ContentChild.
ContentChild* contentChild = ContentChild::GetSingleton();
if (NS_WARN_IF(!contentChild)) {
return false;
}
nsCString message;
bool result = false;
bool ok = contentChild->SendIsGMPPresentOnDisk(nsString(aKeySystem), nsCString(aVersion),
&result, &message);
aOutMessage = message;
return ok && result;
}
bool isPresent = true;
#if XP_WIN
if (aKeySystem.EqualsLiteral("com.adobe.primetime")) {
if (!AdobePluginDLLExists(aVersion)) {
NS_WARNING("Adobe EME plugin disappeared from disk!");
aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not");
isPresent = false;
}
if (!AdobePluginVoucherExists(aVersion)) {
NS_WARNING("Adobe EME voucher disappeared from disk!");
aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not");
isPresent = false;
}
if (!isPresent) {
// Reset the prefs that Firefox's GMP downloader sets, so that
// Firefox will try to download the plugin next time the updater runs.
Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate");
Preferences::ClearUser("media.gmp-eme-adobe.version");
} else if (!EMEVoucherFileExists()) {
// Gecko doesn't have a voucher file for the plugin-container.
// Adobe EME isn't going to work, so don't advertise that it will.
aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present");
isPresent = false;
}
}
#endif
return isPresent;
}
static MediaKeySystemStatus
EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
const nsAString& aKeySystem,
@ -179,29 +232,9 @@ EnsureMinCDMVersion(mozIGeckoMediaPluginService* aGMPService,
return MediaKeySystemStatus::Cdm_not_installed;
}
#ifdef XP_WIN
if (aKeySystem.EqualsLiteral("com.adobe.primetime")) {
// Verify that anti-virus hasn't "helpfully" deleted the Adobe GMP DLL,
// as we suspect may happen (Bug 1160382).
bool somethingMissing = false;
if (!AdobePluginDLLExists(versionStr)) {
aOutMessage = NS_LITERAL_CSTRING("Adobe DLL was expected to be on disk but was not");
somethingMissing = true;
}
if (!AdobePluginVoucherExists(versionStr)) {
aOutMessage = NS_LITERAL_CSTRING("Adobe plugin voucher was expected to be on disk but was not");
somethingMissing = true;
}
if (somethingMissing) {
NS_WARNING("Adobe EME plugin or voucher disappeared from disk!");
// Reset the prefs that Firefox's GMP downloader sets, so that
// Firefox will try to download the plugin next time the updater runs.
Preferences::ClearUser("media.gmp-eme-adobe.lastUpdate");
Preferences::ClearUser("media.gmp-eme-adobe.version");
return MediaKeySystemStatus::Cdm_not_installed;
}
if (!MediaKeySystemAccess::IsGMPPresentOnDisk(aKeySystem, versionStr, aOutMessage)) {
return MediaKeySystemStatus::Cdm_not_installed;
}
#endif
nsresult rv;
int32_t version = versionStr.ToInteger(&rv);
@ -256,12 +289,6 @@ MediaKeySystemAccess::GetKeySystemStatus(const nsAString& aKeySystem,
return MediaKeySystemStatus::Cdm_not_supported;
}
#endif
if (!EMEVoucherFileExists()) {
// Gecko doesn't have a voucher file for the plugin-container.
// Adobe EME isn't going to work, so don't advertise that it will.
aOutMessage = NS_LITERAL_CSTRING("Plugin-container voucher not present");
return MediaKeySystemStatus::Cdm_not_supported;
}
return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
}
#endif

View File

@ -59,6 +59,10 @@ public:
const nsAString& aKeySystem,
MediaKeySystemStatus aStatus);
static bool IsGMPPresentOnDisk(const nsAString& aKeySystem,
const nsACString& aVersion,
nsACString& aOutMessage);
private:
nsCOMPtr<nsPIDOMWindow> mParent;
const nsString mKeySystem;

View File

@ -41,4 +41,6 @@ UNIFIED_SOURCES += [
'MediaKeySystemAccessManager.cpp',
]
include('/ipc/chromium/chromium-config.mozbuild')
FINAL_LIBRARY = 'xul'

View File

@ -433,7 +433,7 @@ GMPChild::GetUTF8LibPath(nsACString& aOutLibPath)
}
bool
GMPChild::RecvStartPlugin()
GMPChild::AnswerStartPlugin()
{
LOGD("%s", __FUNCTION__);

View File

@ -56,7 +56,7 @@ private:
bool GetUTF8LibPath(nsACString& aOutLibPath);
virtual bool RecvSetNodeId(const nsCString& aNodeId) override;
virtual bool RecvStartPlugin() override;
virtual bool AnswerStartPlugin() override;
virtual PCrashReporterChild* AllocPCrashReporterChild(const NativeThreadId& aThread) override;
virtual bool DeallocPCrashReporterChild(PCrashReporterChild*) override;

View File

@ -164,17 +164,14 @@ GMPParent::LoadProcess()
bool ok = SendSetNodeId(mNodeId);
if (!ok) {
LOGD("%s: Failed to send node id to child process", __FUNCTION__);
mProcess->Delete();
mProcess = nullptr;
return NS_ERROR_FAILURE;
}
LOGD("%s: Sent node id to child process", __FUNCTION__);
ok = SendStartPlugin();
// Intr call to block initialization on plugin load.
ok = CallStartPlugin();
if (!ok) {
LOGD("%s: Failed to send start to child process", __FUNCTION__);
mProcess->Delete();
mProcess = nullptr;
return NS_ERROR_FAILURE;
}
LOGD("%s: Sent StartPlugin to child process", __FUNCTION__);

View File

@ -181,6 +181,40 @@ GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
return NS_OK;
}
NS_IMETHODIMP
GeckoMediaPluginServiceChild::UpdateTrialCreateState(const nsAString& aKeySystem,
uint32_t aState)
{
if (NS_GetCurrentThread() != mGMPThread) {
mGMPThread->Dispatch(NS_NewRunnableMethodWithArgs<nsString, uint32_t>(
this, &GeckoMediaPluginServiceChild::UpdateTrialCreateState,
aKeySystem, aState), NS_DISPATCH_NORMAL);
return NS_OK;
}
class Callback : public GetServiceChildCallback
{
public:
Callback(const nsAString& aKeySystem, uint32_t aState)
: mKeySystem(aKeySystem)
, mState(aState)
{ }
virtual void Done(GMPServiceChild* aService) override
{
aService->SendUpdateGMPTrialCreateState(mKeySystem, mState);
}
private:
nsString mKeySystem;
uint32_t mState;
};
UniquePtr<GetServiceChildCallback> callback(new Callback(aKeySystem, aState));
GetServiceChild(Move(callback));
return NS_OK;
}
NS_IMETHODIMP
GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject,
const char* aTopic,

View File

@ -49,6 +49,8 @@ public:
const nsAString& aTopLevelOrigin,
bool aInPrivateBrowsingMode,
UniquePtr<GetNodeIdCallback>&& aCallback) override;
NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem,
uint32_t aState) override;
NS_DECL_NSIOBSERVER

View File

@ -9,6 +9,9 @@
#include "mozilla/Logging.h"
#include "GMPParent.h"
#include "GMPVideoDecoderParent.h"
#ifdef MOZ_EME
#include "mozilla/dom/GMPVideoDecoderTrialCreator.h"
#endif
#include "nsIObserverService.h"
#include "GeckoChildProcessHost.h"
#include "mozilla/Preferences.h"
@ -1202,6 +1205,22 @@ GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
return rv;
}
NS_IMETHODIMP
GeckoMediaPluginServiceParent::UpdateTrialCreateState(const nsAString& aKeySystem,
uint32_t aState)
{
#ifdef MOZ_EME
nsString keySystem(aKeySystem);
NS_DispatchToMainThread(NS_NewRunnableFunction([keySystem, aState] {
mozilla::dom::GMPVideoDecoderTrialCreator::UpdateTrialCreateState(keySystem, aState);
}));
return NS_OK;
#else
return NS_ERROR_FAILURE;
#endif
}
static bool
ExtractHostName(const nsACString& aOrigin, nsACString& aOutData)
{
@ -1571,6 +1590,14 @@ GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
return NS_SUCCEEDED(rv);
}
bool
GMPServiceParent::RecvUpdateGMPTrialCreateState(const nsString& aKeySystem,
const uint32_t& aState)
{
mService->UpdateTrialCreateState(aKeySystem, aState);
return true;
}
/* static */
bool
GMPServiceParent::RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,

View File

@ -43,6 +43,8 @@ public:
const nsAString& aTopLevelOrigin,
bool aInPrivateBrowsingMode,
UniquePtr<GetNodeIdCallback>&& aCallback) override;
NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem,
uint32_t aState) override;
NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
NS_DECL_NSIOBSERVER
@ -219,6 +221,8 @@ public:
nsTArray<nsCString>&& aTags,
bool* aHasPlugin,
nsCString* aVersion);
virtual bool RecvUpdateGMPTrialCreateState(const nsString& aKeySystem,
const uint32_t& aState) override;
virtual void ActorDestroy(ActorDestroyReason aWhy) override;

View File

@ -34,7 +34,7 @@ parent:
child:
async BeginAsyncShutdown();
async CrashPluginNow();
async StartPlugin();
intr StartPlugin();
async SetNodeId(nsCString nodeId);
async CloseActive();
};

View File

@ -21,6 +21,8 @@ parent:
sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
bool inPrivateBrowsing)
returns (nsCString id);
async UpdateGMPTrialCreateState(nsString keySystem, uint32_t status);
};
} // namespace gmp

View File

@ -52,7 +52,7 @@ native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>
native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
[scriptable, uuid(787cf744-eea8-48c8-b692-376e0485ef97)]
[scriptable, uuid(661492d6-726b-4ba0-8e6e-14bfaf2b62e4)]
interface mozIGeckoMediaPluginService : nsISupports
{
@ -147,4 +147,10 @@ interface mozIGeckoMediaPluginService : nsISupports
in AString topLevelOrigin,
in bool inPrivateBrowsingMode,
in GetNodeIdCallback callback);
/**
* Stores the result of trying to create a decoder for the given keysystem.
*/
[noscript]
void updateTrialCreateState(in AString keySystem, in uint32_t status);
};

View File

@ -271,7 +271,6 @@ public:
mCompleteMediaHeaderRange = MediaByteRange(mapping[0].mSyncOffset,
mapping[0].mEndOffset);
}
mLastMapping = Some(mapping[completeIdx]);
if (foundNewCluster && mOffset >= mapping[endIdx].mEndOffset) {
// We now have all information required to delimit a complete cluster.
@ -288,12 +287,24 @@ public:
mParser.EndSegmentOffset(mapping[endIdx].mClusterEndOffset));
}
if (!completeIdx) {
Maybe<WebMTimeDataOffset> previousMapping;
if (completeIdx) {
previousMapping = Some(mapping[completeIdx - 1]);
} else {
previousMapping = mLastMapping;
}
mLastMapping = Some(mapping[completeIdx]);
if (!previousMapping && completeIdx + 1u >= mapping.Length()) {
// We have no previous nor next block available,
// so we can't estimate this block's duration.
return false;
}
uint64_t frameDuration =
mapping[completeIdx].mTimecode - mapping[completeIdx - 1].mTimecode;
uint64_t frameDuration = (completeIdx + 1u < mapping.Length())
? mapping[completeIdx + 1].mTimecode - mapping[completeIdx].mTimecode
: mapping[completeIdx].mTimecode - previousMapping.ref().mTimecode;
aStart = mapping[0].mTimecode / NS_PER_USEC;
aEnd = (mapping[completeIdx].mTimecode + frameDuration) / NS_PER_USEC;

View File

@ -11,7 +11,7 @@
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
PARALLEL_TESTS = 1;
var manager = new MediaTestManager;
// Fragment parameters to try. These tests
@ -78,9 +78,7 @@ function startTest(test, token) {
ok(a, name + ": " + msg);
}}(name);
var localFinish = function(v, manager) { return function() {
if (v.parentNode) {
v.parentNode.removeChild(v);
}
removeNodeAndSource(v);
manager.finished(v.token);
}}(v, manager);
window['test_fragment_play'](v, test.start, test.end, localIs, localOk, localFinish);

View File

@ -131,8 +131,6 @@ WebMDemuxer::WebMDemuxer(MediaResource* aResource, bool aIsMediaSource)
, mVideoTrack(0)
, mAudioTrack(0)
, mSeekPreroll(0)
, mLastAudioFrameTime(0)
, mLastVideoFrameTime(0)
, mAudioCodec(-1)
, mVideoCodec(-1)
, mHasVideo(false)
@ -507,27 +505,37 @@ WebMDemuxer::GetNextPacket(TrackInfo::TrackType aType, MediaRawDataQueue *aSampl
// the timestamp of the next packet for this track. If we've reached the
// end of the resource, use the file's duration as the end time of this
// video frame.
int64_t next_tstamp = 0;
int64_t next_tstamp = INT64_MIN;
if (aType == TrackInfo::kAudioTrack) {
nsRefPtr<NesteggPacketHolder> next_holder(NextPacket(aType));
if (next_holder) {
next_tstamp = next_holder->Timestamp();
PushAudioPacket(next_holder);
} else {
} else if (!mIsMediaSource ||
(mIsMediaSource && mLastAudioFrameTime.isSome())) {
next_tstamp = tstamp;
next_tstamp += tstamp - mLastAudioFrameTime;
next_tstamp += tstamp - mLastAudioFrameTime.refOr(0);
} else {
PushAudioPacket(holder);
}
mLastAudioFrameTime = tstamp;
mLastAudioFrameTime = Some(tstamp);
} else if (aType == TrackInfo::kVideoTrack) {
nsRefPtr<NesteggPacketHolder> next_holder(NextPacket(aType));
if (next_holder) {
next_tstamp = next_holder->Timestamp();
PushVideoPacket(next_holder);
} else {
} else if (!mIsMediaSource ||
(mIsMediaSource && mLastVideoFrameTime.isSome())) {
next_tstamp = tstamp;
next_tstamp += tstamp - mLastVideoFrameTime;
next_tstamp += tstamp - mLastVideoFrameTime.refOr(0);
} else {
PushVideoPacket(holder);
}
mLastVideoFrameTime = tstamp;
mLastVideoFrameTime = Some(tstamp);
}
if (mIsMediaSource && next_tstamp == INT64_MIN) {
return false;
}
int64_t discardPadding = 0;
@ -657,7 +665,7 @@ WebMDemuxer::GetNextKeyframeTime()
EnsureUpToDateIndex();
uint64_t keyframeTime;
uint64_t lastFrame =
media::TimeUnit::FromMicroseconds(mLastVideoFrameTime).ToNanoseconds();
media::TimeUnit::FromMicroseconds(mLastVideoFrameTime.refOr(0)).ToNanoseconds();
if (!mBufferedState->GetNextKeyframeTime(lastFrame, &keyframeTime) ||
keyframeTime <= lastFrame) {
return -1;
@ -725,8 +733,8 @@ WebMDemuxer::SeekInternal(const media::TimeUnit& aTarget)
WEBM_DEBUG("got offset from buffered state: %" PRIu64 "", offset);
}
mLastAudioFrameTime = 0;
mLastVideoFrameTime = 0;
mLastAudioFrameTime.reset();
mLastVideoFrameTime.reset();
return NS_OK;
}

View File

@ -156,11 +156,10 @@ private:
// Nanoseconds to discard after seeking.
uint64_t mSeekPreroll;
int64_t mLastAudioFrameTime;
// Calculate the frame duration from the last decodeable frame using the
// previous frame's timestamp. In NS.
int64_t mLastVideoFrameTime;
Maybe<int64_t> mLastAudioFrameTime;
Maybe<int64_t> mLastVideoFrameTime;
// Codec ID of audio track
int mAudioCodec;

View File

@ -409,6 +409,22 @@ public:
}
};
class ReleaseNotificationRunnable final : public NotificationWorkerRunnable
{
Notification* mNotification;
public:
explicit ReleaseNotificationRunnable(Notification* aNotification)
: NotificationWorkerRunnable(aNotification->mWorkerPrivate)
, mNotification(aNotification)
{}
void
WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
mNotification->ReleaseObject();
}
};
// Create one whenever you require ownership of the notification. Use with
// UniquePtr<>. See Notification.h for details.
class NotificationRef final {
@ -452,18 +468,31 @@ public:
~NotificationRef()
{
if (Initialized() && mNotification) {
if (mNotification->mWorkerPrivate && NS_IsMainThread()) {
nsRefPtr<ReleaseNotificationControlRunnable> r =
new ReleaseNotificationControlRunnable(mNotification);
AutoSafeJSContext cx;
if (!r->Dispatch(cx)) {
MOZ_CRASH("Will leak worker thread Notification!");
Notification* notification = mNotification;
mNotification = nullptr;
if (notification->mWorkerPrivate && NS_IsMainThread()) {
// Try to pass ownership back to the worker. If the dispatch succeeds we
// are guaranteed this runnable will run, and that it will run after queued
// event runnables, so event runnables will have a safe pointer to the
// Notification.
//
// If the dispatch fails, the worker isn't running anymore and the event
// runnables have already run or been canceled. We can use a control
// runnable to release the reference.
nsRefPtr<ReleaseNotificationRunnable> r =
new ReleaseNotificationRunnable(notification);
AutoJSAPI jsapi;
jsapi.Init();
if (!r->Dispatch(jsapi.cx())) {
nsRefPtr<ReleaseNotificationControlRunnable> r =
new ReleaseNotificationControlRunnable(notification);
MOZ_ALWAYS_TRUE(r->Dispatch(jsapi.cx()));
}
} else {
mNotification->AssertIsOnTargetThread();
mNotification->ReleaseObject();
notification->AssertIsOnTargetThread();
notification->ReleaseObject();
}
mNotification = nullptr;
}
}
@ -973,44 +1002,6 @@ protected:
AssertIsOnMainThread();
MOZ_ASSERT(mNotificationRef);
Notification* notification = mNotificationRef->GetNotification();
if (!notification) {
return;
}
// Try to pass ownership back to the worker. If the dispatch succeeds we
// are guaranteed this runnable will run, and that it will run after queued
// event runnables, so event runnables will have a safe pointer to the
// Notification.
//
// If the dispatch fails, the worker isn't running anymore and the event
// runnables have already run. We can just let the standard NotificationRef
// release routine take over when ReleaseNotificationRunnable gets deleted.
class ReleaseNotificationRunnable final : public NotificationWorkerRunnable
{
UniquePtr<NotificationRef> mNotificationRef;
public:
explicit ReleaseNotificationRunnable(UniquePtr<NotificationRef> aRef)
: NotificationWorkerRunnable(aRef->GetNotification()->mWorkerPrivate)
, mNotificationRef(Move(aRef))
{}
void
WorkerRunInternal(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
{
UniquePtr<NotificationRef> ref;
mozilla::Swap(ref, mNotificationRef);
// Gets released at the end of the function.
}
};
nsRefPtr<ReleaseNotificationRunnable> r =
new ReleaseNotificationRunnable(Move(mNotificationRef));
notification = nullptr;
AutoJSAPI jsapi;
jsapi.Init();
r->Dispatch(jsapi.cx());
}
};

View File

@ -89,22 +89,12 @@ public:
* Note that the Notification's JS wrapper does it's standard
* AddRef()/Release() and is not affected by any of this.
*
* There is one case related to the WorkerNotificationObserver having to
* dispatch WorkerRunnables to the worker thread which will use the
* Notification object. We can end up in a situation where an event runnable is
* dispatched to the worker, gets queued in the worker's event queue, but then,
* the worker yields to the main thread. Here the main thread observer is
* destroyed, which frees its NotificationRef. The NotificationRef dispatches
* a ControlRunnable to the worker, which runs before the event runnable,
* leading to the event runnable possibly not having a valid Notification
* reference.
* We solve this problem by having WorkerNotificationObserver's dtor
* dispatching a standard WorkerRunnable to do the release (this guarantees the
* ordering of the release is after the event runnables). All WorkerRunnables
* that get dispatched successfully are guaranteed to run on the worker before
* it shuts down. If that dispatch fails, the standard ControlRunnable based
* shutdown is acceptable since the already dispatched event runnables have
* already run or canceled (the worker is already past Running).
* Since the worker event queue can have runnables that will dispatch events on
* the Notification, the NotificationRef destructor will first try to release
* the Notification by dispatching a normal runnable to the worker so that it is
* queued after any event runnables. If that dispatch fails, it means the worker
* is no longer running and queued WorkerRunnables will be canceled, so we
* dispatch a control runnable instead.
*
*/
class Notification : public DOMEventTargetHelper

View File

@ -19,5 +19,6 @@ skip-if = os == "android" || toolkit == "gonk"
skip-if = os == "android" || toolkit == "gonk"
[test_multiple_register_different_scope.html]
skip-if = os == "android" || toolkit == "gonk"
[test_try_registering_offline_disabled.html]
skip-if = os == "android" || toolkit == "gonk"
# Disabled for too many intermittent failures (bug 1164432)
# [test_try_registering_offline_disabled.html]
# skip-if = os == "android" || toolkit == "gonk"

View File

@ -1125,7 +1125,7 @@ function testRedirects() {
},
],
},
{ pass: 0,
{ pass: 1,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
@ -1140,6 +1140,24 @@ function testRedirects() {
],
},
{ pass: 0,
method: "POST",
body: "hi there",
headers: { "Content-Type": "text/plain",
"my-header": "myValue",
},
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://test1.example.com",
allowOrigin: origin,
allowHeaders: "my-header",
},
{ server: "http://test2.example.com",
allowOrigin: origin,
allowHeaders: "my-header",
}
],
},
{ pass: 1,
method: "DELETE",
hops: [{ server: "http://mochi.test:8888",
},
@ -1148,6 +1166,18 @@ function testRedirects() {
},
],
},
{ pass: 0,
method: "DELETE",
hops: [{ server: "http://mochi.test:8888",
},
{ server: "http://test1.example.com",
allowOrigin: origin,
},
{ server: "http://test2.example.com",
allowOrigin: origin,
},
],
},
{ pass: 0,
method: "POST",
body: "hi there",

View File

@ -23,6 +23,7 @@ interface Request {
readonly attribute RequestMode mode;
readonly attribute RequestCredentials credentials;
readonly attribute RequestCache cache;
readonly attribute RequestRedirect redirect;
[Throws,
NewObject] Request clone();
@ -40,6 +41,7 @@ dictionary RequestInit {
RequestMode mode;
RequestCredentials credentials;
RequestCache cache;
RequestRedirect redirect;
};
// Gecko currently does not ship RequestContext, so please don't use it in IDL
@ -61,3 +63,4 @@ enum RequestContext {
enum RequestMode { "same-origin", "no-cors", "cors", "cors-with-forced-preflight" };
enum RequestCredentials { "omit", "same-origin", "include" };
enum RequestCache { "default", "no-store", "reload", "no-cache", "force-cache", "only-if-cached" };
enum RequestRedirect { "follow", "error", "manual" };

View File

@ -34,4 +34,4 @@ dictionary ResponseInit {
HeadersInit headers;
};
enum ResponseType { "basic", "cors", "default", "error", "opaque" };
enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };

View File

@ -130,7 +130,8 @@ public:
return rv;
}
mChannel->SynthesizeStatus(mInternalResponse->GetStatus(), mInternalResponse->GetStatusText());
mChannel->SynthesizeStatus(mInternalResponse->GetUnfilteredStatus(),
mInternalResponse->GetUnfilteredStatusText());
nsAutoTArray<InternalHeaders::Entry, 5> entries;
mInternalResponse->UnfilteredHeaders()->GetEntries(entries);
@ -148,18 +149,21 @@ class RespondWithHandler final : public PromiseNativeHandler
{
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
RequestMode mRequestMode;
bool mIsClientRequest;
const RequestMode mRequestMode;
const bool mIsClientRequest;
const bool mIsNavigationRequest;
public:
NS_DECL_ISUPPORTS
RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
RequestMode aRequestMode, bool aIsClientRequest)
RequestMode aRequestMode, bool aIsClientRequest,
bool aIsNavigationRequest)
: mInterceptedChannel(aChannel)
, mServiceWorker(aServiceWorker)
, mRequestMode(aRequestMode)
, mIsClientRequest(aIsClientRequest)
, mIsNavigationRequest(aIsNavigationRequest)
{
}
@ -264,12 +268,14 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
return;
}
// Section 4.2, step 2.2:
// Section "HTTP Fetch", step 2.2:
// If one of the following conditions is true, return a network error:
// * response's type is "error".
// * request's mode is not "no-cors" and response's type is "opaque".
// * request is a client request and response's type is neither "basic"
// nor "default".
// * request is not a navigation request and response's type is
// "opaqueredirect".
if (response->Type() == ResponseType::Error) {
autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE);
@ -281,12 +287,19 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
return;
}
// TODO: remove this case as its no longer in the spec (bug 1184967)
if (mIsClientRequest && response->Type() != ResponseType::Basic &&
response->Type() != ResponseType::Default) {
response->Type() != ResponseType::Default &&
response->Type() != ResponseType::Opaqueredirect) {
autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION);
return;
}
if (!mIsNavigationRequest && response->Type() == ResponseType::Opaqueredirect) {
autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_REDIRECT_INTERCEPTION);
return;
}
if (NS_WARN_IF(response->BodyUsed())) {
autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_USED_RESPONSE);
return;
@ -300,7 +313,7 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValu
nsAutoPtr<RespondWithClosure> closure(
new RespondWithClosure(mInterceptedChannel, ir, worker->GetChannelInfo()));
nsCOMPtr<nsIInputStream> body;
ir->GetInternalBody(getter_AddRefs(body));
ir->GetUnfilteredBody(getter_AddRefs(body));
// Errors and redirects may not have a body.
if (body) {
response->SetBodyUsed();
@ -359,7 +372,7 @@ FetchEvent::RespondWith(Promise& aArg, ErrorResult& aRv)
mWaitToRespond = true;
nsRefPtr<RespondWithHandler> handler =
new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(),
ir->IsClientRequest());
ir->IsClientRequest(), ir->IsNavigationRequest());
aArg.AppendNativeHandler(handler);
}

View File

@ -94,6 +94,15 @@ static_assert(nsIHttpChannelInternal::CORS_MODE_CORS == static_cast<uint32_t>(Re
static_assert(nsIHttpChannelInternal::CORS_MODE_CORS_WITH_FORCED_PREFLIGHT == static_cast<uint32_t>(RequestMode::Cors_with_forced_preflight),
"RequestMode enumeration value should match Necko CORS mode value.");
static_assert(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW == static_cast<uint32_t>(RequestRedirect::Follow),
"RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(nsIHttpChannelInternal::REDIRECT_MODE_ERROR == static_cast<uint32_t>(RequestRedirect::Error),
"RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(nsIHttpChannelInternal::REDIRECT_MODE_MANUAL == static_cast<uint32_t>(RequestRedirect::Manual),
"RequestRedirect enumeration value should make Necko Redirect mode value.");
static_assert(3 == static_cast<uint32_t>(RequestRedirect::EndGuard_),
"RequestRedirect enumeration value should make Necko Redirect mode value.");
static StaticRefPtr<ServiceWorkerManager> gInstance;
// Tracks the "dom.disable_open_click_delay" preference. Modified on main
@ -3616,7 +3625,9 @@ class FetchEventRunnable : public WorkerRunnable
nsCString mSpec;
nsCString mMethod;
bool mIsReload;
DebugOnly<bool> mIsHttpChannel;
RequestMode mRequestMode;
RequestRedirect mRequestRedirect;
RequestCredentials mRequestCredentials;
nsContentPolicyType mContentPolicyType;
nsCOMPtr<nsIInputStream> mUploadStream;
@ -3632,7 +3643,9 @@ public:
, mServiceWorker(aServiceWorker)
, mClientInfo(aClientInfo)
, mIsReload(aIsReload)
, mIsHttpChannel(false)
, mRequestMode(RequestMode::No_cors)
, mRequestRedirect(RequestRedirect::Follow)
// By default we set it to same-origin since normal HTTP fetches always
// send credentials to same-origin websites unless explicitly forbidden.
, mRequestCredentials(RequestCredentials::Same_origin)
@ -3688,15 +3701,17 @@ public:
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
if (httpChannel) {
mIsHttpChannel = true;
rv = httpChannel->GetRequestMethod(mMethod);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(httpChannel);
NS_ENSURE_TRUE(internalChannel, NS_ERROR_NOT_AVAILABLE);
uint32_t mode;
internalChannel->GetCorsMode(&mode);
switch (mode) {
uint32_t corsMode;
internalChannel->GetCorsMode(&corsMode);
switch (corsMode) {
case nsIHttpChannelInternal::CORS_MODE_SAME_ORIGIN:
mRequestMode = RequestMode::Same_origin;
break;
@ -3711,6 +3726,11 @@ public:
MOZ_CRASH("Unexpected CORS mode");
}
// This is safe due to static_asserts at top of file.
uint32_t redirectMode;
internalChannel->GetRedirectMode(&redirectMode);
mRequestRedirect = static_cast<RequestRedirect>(redirectMode);
if (loadFlags & nsIRequest::LOAD_ANONYMOUS) {
mRequestCredentials = RequestCredentials::Omit;
} else {
@ -3803,6 +3823,7 @@ private:
reqInit.mHeaders.Value().SetAsHeaders() = headers;
reqInit.mMode.Construct(mRequestMode);
reqInit.mRedirect.Construct(mRequestRedirect);
reqInit.mCredentials.Construct(mRequestCredentials);
ErrorResult result;
@ -3821,6 +3842,11 @@ private:
request->SetContentPolicyType(mContentPolicyType);
// TODO: remove conditional on http here once app protocol support is
// removed from service worker interception
MOZ_ASSERT_IF(mIsHttpChannel && internalReq->IsNavigationRequest(),
request->Redirect() == RequestRedirect::Manual);
RootedDictionary<FetchEventInit> init(aCx);
init.mRequest.Construct();
init.mRequest.Value() = request;

View File

@ -4,7 +4,8 @@ self.addEventListener("install", function(event) {
event.waitUntil(
self.caches.open("origin-cache")
.then(c => {
return c.add(prefix + 'index-https.sjs');
return c.add(new Request(prefix + 'index-https.sjs',
{ redirect: 'manual' }));
})
);
});

View File

@ -1 +0,0 @@
Access-Control-Allow-Origin: https://example.com

View File

@ -6,8 +6,10 @@ self.addEventListener("install", function(event) {
.then(c => {
return Promise.all(
[
c.add(prefix + 'index.sjs'),
c.add(prefix + 'index-to-https.sjs'),
c.add(new Request(prefix + 'index.sjs',
{ redirect: 'manual' } )),
c.add(new Request(prefix + 'index-to-https.sjs',
{ redirect: 'manual' } ))
]
);
})

View File

@ -1 +0,0 @@
Access-Control-Allow-Origin: http://mochi.test:8888

View File

@ -53,13 +53,11 @@ support-files =
fetch/origin/index.sjs
fetch/origin/index-to-https.sjs
fetch/origin/realindex.html
fetch/origin/realindex.html^headers^
fetch/origin/register.html
fetch/origin/unregister.html
fetch/origin/origin_test.js
fetch/origin/https/index-https.sjs
fetch/origin/https/realindex.html
fetch/origin/https/realindex.html^headers^
fetch/origin/https/register.html
fetch/origin/https/unregister.html
fetch/origin/https/origin_test.js

View File

@ -1268,7 +1268,7 @@ nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
(aVisitor.mEvent->mMessage == eMouseClick ||
aVisitor.mEvent->mMessage == eMouseDoubleClick ||
aVisitor.mEvent->mMessage == NS_XUL_COMMAND ||
aVisitor.mEvent->mMessage == NS_CONTEXTMENU ||
aVisitor.mEvent->mMessage == eContextMenu ||
aVisitor.mEvent->mMessage == NS_DRAGDROP_START ||
aVisitor.mEvent->mMessage == NS_DRAGDROP_GESTURE)) {
// Don't propagate these events from native anonymous scrollbar.

View File

@ -90,7 +90,7 @@ nsHTMLEditorEventListener::MouseDown(nsIDOMMouseEvent* aMouseEvent)
// Detect only "context menu" click
// XXX This should be easier to do!
// But eDOMEvents_contextmenu and NS_CONTEXTMENU is not exposed in any event
// But eDOMEvents_contextmenu and eContextMenu is not exposed in any event
// interface :-(
int16_t buttonNumber;
nsresult rv = aMouseEvent->GetButton(&buttonNumber);

View File

@ -893,7 +893,7 @@ APZCTreeManager::UpdateWheelTransaction(WidgetInputEvent& aEvent)
case eMouseDown:
case eMouseDoubleClick:
case eMouseClick:
case NS_CONTEXTMENU:
case eContextMenu:
case NS_DRAGDROP_DROP:
txn->EndTransaction();
return;

View File

@ -747,8 +747,10 @@ FilterNodeFromPrimitiveDescription(const FilterPrimitiveDescription& aDescriptio
BLEND_MODE_LUMINOSITY
};
filter->SetAttribute(ATT_BLEND_BLENDMODE, (uint32_t)blendModes[mode]);
filter->SetInput(IN_BLEND_IN, aSources[0]);
filter->SetInput(IN_BLEND_IN2, aSources[1]);
// The correct input order for both software and D2D filters is flipped
// from our source order, so flip here.
filter->SetInput(IN_BLEND_IN, aSources[1]);
filter->SetInput(IN_BLEND_IN2, aSources[0]);
}
return filter.forget();
}

View File

@ -97,7 +97,6 @@
// represented by a "rope", a structure that points to the two original
// strings.
//
//
// We intend to use ubi::Node to write tools that report memory usage, so it's
// important that ubi::Node accurately portray how much memory nodes consume.
// Thus, for example, when data that apparently belongs to multiple nodes is
@ -142,6 +141,25 @@
// If this restriction prevents us from implementing interesting tools, we may
// teach the GC how to root ubi::Nodes, fix up hash tables that use them as
// keys, etc.
//
//
// Hostile Graph Structure
//
// Analyses consuming ubi::Node graphs must be robust when presented with graphs
// that are deliberately constructed to exploit their weaknesses. When operating
// on live graphs, web content has control over the object graph, and less
// direct control over shape and string structure, and analyses should be
// prepared to handle extreme cases gracefully. For example, if an analysis were
// to use the C++ stack in a depth-first traversal, carefully constructed
// content could cause the analysis to overflow the stack.
//
// When ubi::Nodes refer to nodes deserialized from a heap snapshot, analyses
// must be even more careful: since snapshots often come from potentially
// compromised e10s content processes, even properties normally guaranteed by
// the platform (the proper linking of DOM nodes, for example) might be
// corrupted. While it is the deserializer's responsibility to check the basic
// structure of the snapshot file, the analyses should be prepared for ubi::Node
// graphs constructed from snapshots to be even more bizarre.
class JSAtom;

View File

@ -253,7 +253,7 @@ function ArrayMap(callbackfn/*, thisArg*/) {
var T = arguments.length > 1 ? arguments[1] : void 0;
/* Step 6. */
var A = NewDenseArray(len);
var A = std_Array(len);
/* Step 7-8. */
/* Step a (implicit), and d. */
@ -718,9 +718,7 @@ function ArrayIteratorNext() {
}
if (itemKind === ITEM_KIND_KEY_AND_VALUE) {
var pair = NewDenseArray(2);
pair[0] = index;
pair[1] = a[index];
var pair = [index, a[index]];
result.value = pair;
return result;
}
@ -807,7 +805,7 @@ function ArrayFrom(items, mapfn=undefined, thisArg=undefined) {
var len = ToLength(arrayLike.length);
// Steps 12-14.
var A = IsConstructor(C) ? new C(len) : NewDenseArray(len);
var A = IsConstructor(C) ? new C(len) : std_Array(len);
// Steps 15-16.
for (var k = 0; k < len; k++) {

View File

@ -1405,9 +1405,10 @@ Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
break;
case Method:
MOZ_ASSERT(generatorKind == NotGenerator || generatorKind == StarGenerator);
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_METHOD
: JSFunction::INTERPRETED_METHOD_GENERATOR);
if (generatorKind == NotGenerator)
flags = JSFunction::INTERPRETED_METHOD;
else
flags = JSFunction::INTERPRETED_METHOD_GENERATOR;
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
case ClassConstructor:
@ -1424,9 +1425,8 @@ Parser<ParseHandler>::newFunction(HandleAtom atom, FunctionSyntaxKind kind,
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
break;
default:
flags = (generatorKind == NotGenerator
? JSFunction::INTERPRETED_NORMAL
: JSFunction::INTERPRETED_GENERATOR);
flags = JSFunction::INTERPRETED_NORMAL;
break;
}
fun = NewFunctionWithProto(context, nullptr, 0, flags, nullptr, atom, proto,

View File

@ -1,9 +0,0 @@
function t() {
var o = {l: 0xfffffffff};
var l = o.l - 0xffffffffe;
var a = getSelfHostedValue('NewDenseArray');
var arr = a(l);
assertEq(arr.length, 1);
}
t();
t();

View File

@ -0,0 +1,5 @@
// |jit-test| error: 987
var obj = {length: -1, 0: 0};
Array.prototype.map.call(obj, function () {
throw 987;
});

View File

@ -314,7 +314,7 @@ JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type)
// Pop arguments off the stack.
// s0 <- 8*argc (size of all arguments we pushed on the stack)
masm.pop(s0);
masm.rshiftPtr(Imm32(4), s0);
masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), s0);
masm.addPtr(s0, StackPointer);
// Store the returned value into the slotVp

View File

@ -754,7 +754,6 @@ bool intrinsic_ToString(JSContext* cx, unsigned argc, Value* vp);
bool intrinsic_IsCallable(JSContext* cx, unsigned argc, Value* vp);
bool intrinsic_ThrowRangeError(JSContext* cx, unsigned argc, Value* vp);
bool intrinsic_ThrowTypeError(JSContext* cx, unsigned argc, Value* vp);
bool intrinsic_NewDenseArray(JSContext* cx, unsigned argc, Value* vp);
bool intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp);
bool intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp);

View File

@ -81,14 +81,13 @@ class JSFunction : public js::NativeObject
ASMJS_CTOR = ASMJS_KIND | NATIVE_CTOR,
ASMJS_LAMBDA_CTOR = ASMJS_KIND | NATIVE_CTOR | LAMBDA,
INTERPRETED_METHOD = INTERPRETED | METHOD_KIND,
INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND,
INTERPRETED_METHOD_GENERATOR = INTERPRETED | METHOD_KIND | CONSTRUCTOR,
INTERPRETED_CLASS_CONSTRUCTOR = INTERPRETED | CLASSCONSTRUCTOR_KIND | CONSTRUCTOR,
INTERPRETED_GETTER = INTERPRETED | GETTER_KIND,
INTERPRETED_SETTER = INTERPRETED | SETTER_KIND,
INTERPRETED_LAMBDA = INTERPRETED | LAMBDA | CONSTRUCTOR,
INTERPRETED_LAMBDA_ARROW = INTERPRETED | LAMBDA | ARROW_KIND,
INTERPRETED_NORMAL = INTERPRETED | CONSTRUCTOR,
INTERPRETED_GENERATOR = INTERPRETED,
NO_XDR_FLAGS = RESOLVED_LENGTH | RESOLVED_NAME,
STABLE_ACROSS_CLONES = IS_FUN_PROTO | CONSTRUCTOR | EXPR_BODY | HAS_GUESSED_ATOM |

View File

@ -67,7 +67,7 @@ assertEq(next.value.hello, 2);
assertEq(next.value.world, 3);
// prototype property
assertEq(b.g.hasOwnProperty("prototype"), false);
assertEq(b.g.hasOwnProperty("prototype"), true);
// Strict mode
a = {*b(c){"use strict";yield c;}};
@ -75,8 +75,13 @@ assertEq(a.b(1).next().value, 1);
a = {*["b"](c){"use strict";return c;}};
assertEq(a.b(1).next().value, 1);
// Generators should not have [[Construct]]
// Constructing
a = {*g() { yield 1; }}
assertThrowsInstanceOf(() => { new a.g }, TypeError);
it = new a.g;
next = it.next();
assertEq(next.done, false);
assertEq(next.value, 1);
next = it.next();
assertEq(next.done, true);
reportCompare(0, 0, "ok");

View File

@ -13,17 +13,23 @@ class TestClass {
var test = new TestClass();
assertEq(test.constructor.hasOwnProperty('prototype'), true);
var hasPrototype = [
test.constructor,
test.generator,
TestClass.staticGenerator
]
for (var fun of hasPrototype) {
assertEq(fun.hasOwnProperty('prototype'), true);
}
var hasNoPrototype = [
test.method,
test.generator,
TestClass.staticGenerator,
Object.getOwnPropertyDescriptor(test.__proto__, 'getter').get,
Object.getOwnPropertyDescriptor(test.__proto__, 'setter').set,
TestClass.staticMethod,
Object.getOwnPropertyDescriptor(TestClass, 'staticGetter').get,
Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set
Object.getOwnPropertyDescriptor(TestClass, 'staticSetter').set,
]
for (var fun of hasNoPrototype) {

View File

@ -10,6 +10,10 @@ const ITERATIONS = 25;
for (let i = 0; i < ITERATIONS; i++)
assertEq(generatorNewTarget(undefined).next().value(), undefined);
for (let i = 0; i < ITERATIONS; i++)
assertEq(new generatorNewTarget(generatorNewTarget).next().value(),
generatorNewTarget);
// also check to make sure it's useful in yield inside generators.
// Plus, this code is so ugly, how could it not be a test? ;)
// Thanks to anba for supplying this ludicrous expression.

Some files were not shown because too many files have changed in this diff Show More