mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
merge mozilla-inbound to mozilla-central a=merge
This commit is contained in:
commit
4d2157e2f8
@ -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.
|
||||
|
@ -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">
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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" : "");
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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]
|
||||
|
73
dom/base/test/test_audioNotificationWithEarlyPlay.html
Normal file
73
dom/base/test/test_audioNotificationWithEarlyPlay.html
Normal 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>
|
||||
|
35
dom/cache/CacheStorage.cpp
vendored
35
dom/cache/CacheStorage.cpp
vendored
@ -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)
|
||||
|
3
dom/cache/CacheStorage.h
vendored
3
dom/cache/CacheStorage.h
vendored
@ -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,
|
||||
|
2
dom/cache/CacheTypes.ipdlh
vendored
2
dom/cache/CacheTypes.ipdlh
vendored
@ -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
|
||||
|
2
dom/cache/DBAction.cpp
vendored
2
dom/cache/DBAction.cpp
vendored
@ -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
529
dom/cache/DBSchema.cpp
vendored
@ -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(¤tVersion);
|
||||
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(¤tVersion);
|
||||
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
|
||||
|
8
dom/cache/DBSchema.h
vendored
8
dom/cache/DBSchema.h
vendored
@ -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
|
||||
|
5
dom/cache/IPCUtils.h
vendored
5
dom/cache/IPCUtils.h
vendored
@ -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
15
dom/cache/Manager.cpp
vendored
@ -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();
|
||||
}
|
||||
}
|
||||
|
31
dom/cache/ManagerId.cpp
vendored
31
dom/cache/ManagerId.cpp
vendored
@ -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
|
||||
"aOrigin,
|
||||
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);
|
||||
}
|
||||
|
9
dom/cache/ManagerId.h
vendored
9
dom/cache/ManagerId.h
vendored
@ -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)
|
||||
|
21
dom/cache/TypeUtils.cpp
vendored
21
dom/cache/TypeUtils.cpp
vendored
@ -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
4
dom/cache/moz.build
vendored
@ -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
77
dom/cache/test/xpcshell/head.js
vendored
Normal 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
142
dom/cache/test/xpcshell/make_profile.js
vendored
Normal 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);
|
||||
});
|
||||
}
|
BIN
dom/cache/test/xpcshell/schema_15_profile.zip
vendored
Normal file
BIN
dom/cache/test/xpcshell/schema_15_profile.zip
vendored
Normal file
Binary file not shown.
38
dom/cache/test/xpcshell/test_migration.js
vendored
Normal file
38
dom/cache/test/xpcshell/test_migration.js
vendored
Normal 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
16
dom/cache/test/xpcshell/xpcshell.ini
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
[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]
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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());
|
||||
|
@ -76,6 +76,12 @@ public:
|
||||
return mRequest->GetCacheMode();
|
||||
}
|
||||
|
||||
RequestRedirect
|
||||
Redirect() const
|
||||
{
|
||||
return mRequest->GetRedirectMode();
|
||||
}
|
||||
|
||||
RequestContext
|
||||
Context() const
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -41,4 +41,6 @@ UNIFIED_SOURCES += [
|
||||
'MediaKeySystemAccessManager.cpp',
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
@ -433,7 +433,7 @@ GMPChild::GetUTF8LibPath(nsACString& aOutLibPath)
|
||||
}
|
||||
|
||||
bool
|
||||
GMPChild::RecvStartPlugin()
|
||||
GMPChild::AnswerStartPlugin()
|
||||
{
|
||||
LOGD("%s", __FUNCTION__);
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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__);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -34,7 +34,7 @@ parent:
|
||||
child:
|
||||
async BeginAsyncShutdown();
|
||||
async CrashPluginNow();
|
||||
async StartPlugin();
|
||||
intr StartPlugin();
|
||||
async SetNodeId(nsCString nodeId);
|
||||
async CloseActive();
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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" };
|
||||
|
@ -34,4 +34,4 @@ dictionary ResponseInit {
|
||||
HeadersInit headers;
|
||||
};
|
||||
|
||||
enum ResponseType { "basic", "cors", "default", "error", "opaque" };
|
||||
enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" };
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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' }));
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -1 +0,0 @@
|
||||
Access-Control-Allow-Origin: https://example.com
|
@ -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' } ))
|
||||
]
|
||||
);
|
||||
})
|
||||
|
@ -1 +0,0 @@
|
||||
Access-Control-Allow-Origin: http://mochi.test:8888
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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++) {
|
||||
|
@ -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,
|
||||
|
@ -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();
|
5
js/src/jit-test/tests/basic/bug1200108.js
Normal file
5
js/src/jit-test/tests/basic/bug1200108.js
Normal file
@ -0,0 +1,5 @@
|
||||
// |jit-test| error: 987
|
||||
var obj = {length: -1, 0: 0};
|
||||
Array.prototype.map.call(obj, function () {
|
||||
throw 987;
|
||||
});
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 |
|
||||
|
@ -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");
|
||||
|
@ -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) {
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user