зеркало из https://github.com/mozilla/gecko-dev.git
Merge m-c to b2g-inbound a=merge
This commit is contained in:
Коммит
e57d551967
3
CLOBBER
3
CLOBBER
|
@ -22,4 +22,5 @@
|
|||
# changes to stick? As of bug 928195, this shouldn't be necessary! Please
|
||||
# don't change CLOBBER for WebIDL changes any more.
|
||||
|
||||
Bug 879861 - Touch CLOBBER because adding a new IDL is a crapshoot these days.
|
||||
Bug 1119335 - (DOMString or sequence<DOMString> or ConstrainDOMStringParameters)
|
||||
needs binding flush (Bug 1103153).
|
||||
|
|
|
@ -320,17 +320,19 @@ AndroidPresenter.prototype.actionInvoked =
|
|||
function AndroidPresenter_actionInvoked(aObject, aActionName) {
|
||||
let state = Utils.getState(aObject);
|
||||
|
||||
// Checkable objects will have a state changed event we will use instead.
|
||||
if (state.contains(States.CHECKABLE)) {
|
||||
return null;
|
||||
// Checkable objects use TalkBack's text derived from the event state,
|
||||
// so we don't populate the text here.
|
||||
let text = '';
|
||||
if (!state.contains(States.CHECKABLE)) {
|
||||
text = Utils.localize(UtteranceGenerator.genForAction(aObject,
|
||||
aActionName));
|
||||
}
|
||||
|
||||
return {
|
||||
type: this.type,
|
||||
details: [{
|
||||
eventType: this.ANDROID_VIEW_CLICKED,
|
||||
text: Utils.localize(UtteranceGenerator.genForAction(aObject,
|
||||
aActionName)),
|
||||
text: text,
|
||||
checked: state.contains(States.CHECKED)
|
||||
}]
|
||||
};
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
// check checkbox
|
||||
[ContentMessages.activateCurrent(),
|
||||
new ExpectedClickAction({ no_android: true }),
|
||||
new ExpectedCheckAction(true, { android_todo: true })],
|
||||
new ExpectedCheckAction(true)],
|
||||
[ContentMessages.simpleMoveNext,
|
||||
new ExpectedCursorChange(['much range', {'string': 'label'}])],
|
||||
[ContentMessages.simpleMoveNext,
|
||||
|
@ -74,7 +74,7 @@
|
|||
// uncheck checkbox
|
||||
[ContentMessages.activateCurrent(),
|
||||
new ExpectedClickAction({ no_android: true }),
|
||||
new ExpectedCheckAction(false, { android_todo: true })],
|
||||
new ExpectedCheckAction(false)],
|
||||
[ContentMessages.simpleMovePrevious,
|
||||
new ExpectedCursorChange(['wow', {'string': 'headingLevel', 'args': [1]}])],
|
||||
[ContentMessages.simpleMovePrevious,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
# UITour
|
||||
host uitour 1 www.mozilla.org
|
||||
host uitour 1 self-repair.mozilla.org
|
||||
host uitour 1 support.mozilla.org
|
||||
host uitour 1 about:home
|
||||
|
||||
|
|
|
@ -307,10 +307,11 @@ function onSearchSubmit(aEvent)
|
|||
if (engineName && searchTerms.length > 0) {
|
||||
// Send an event that will perform a search and Firefox Health Report will
|
||||
// record that a search from about:home has occurred.
|
||||
|
||||
let useNewTab = aEvent && aEvent.button == 1;
|
||||
let eventData = {
|
||||
engineName: engineName,
|
||||
searchTerms: searchTerms
|
||||
searchTerms: searchTerms,
|
||||
useNewTab: useNewTab,
|
||||
};
|
||||
|
||||
if (searchText.hasAttribute("selection-index")) {
|
||||
|
|
|
@ -1064,3 +1064,38 @@ addMessageListener("ContextMenu:SaveVideoFrameAsImage", (message) => {
|
|||
dataURL: canvas.toDataURL("image/jpeg", ""),
|
||||
});
|
||||
});
|
||||
|
||||
addMessageListener("ContextMenu:MediaCommand", (message) => {
|
||||
let media = message.objects.element;
|
||||
|
||||
switch (message.data.command) {
|
||||
case "play":
|
||||
media.play();
|
||||
break;
|
||||
case "pause":
|
||||
media.pause();
|
||||
break;
|
||||
case "mute":
|
||||
media.muted = true;
|
||||
break;
|
||||
case "unmute":
|
||||
media.muted = false;
|
||||
break;
|
||||
case "playbackRate":
|
||||
media.playbackRate = message.data.data;
|
||||
break;
|
||||
case "hidecontrols":
|
||||
media.removeAttribute("controls");
|
||||
break;
|
||||
case "showcontrols":
|
||||
media.setAttribute("controls", "true");
|
||||
break;
|
||||
case "hidestats":
|
||||
case "showstats":
|
||||
let event = media.ownerDocument.createEvent("CustomEvent");
|
||||
event.initCustomEvent("media-showStatistics", false, true,
|
||||
message.data.command == "showstats");
|
||||
media.dispatchEvent(event);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -49,11 +49,12 @@ let gSearch = {
|
|||
let searchText = this._nodes.text;
|
||||
let searchStr = searchText.value;
|
||||
if (this.currentEngineName && searchStr.length) {
|
||||
|
||||
let useNewTab = event && event.button == 1;
|
||||
let eventData = {
|
||||
engineName: this.currentEngineName,
|
||||
searchString: searchStr,
|
||||
whence: "newtab",
|
||||
useNewTab: useNewTab,
|
||||
}
|
||||
|
||||
if (searchText.hasAttribute("selection-index")) {
|
||||
|
@ -244,7 +245,7 @@ let gSearch = {
|
|||
let parent = document.getElementById("newtab-scrollbox");
|
||||
this._suggestionController =
|
||||
new SearchSuggestionUIController(this._nodes.text, parent,
|
||||
() => this.search());
|
||||
event => this.search(event));
|
||||
}
|
||||
this._suggestionController.engineName = engine.name;
|
||||
},
|
||||
|
|
|
@ -1668,37 +1668,10 @@ nsContextMenu.prototype = {
|
|||
},
|
||||
|
||||
mediaCommand : function CM_mediaCommand(command, data) {
|
||||
var media = this.target;
|
||||
|
||||
switch (command) {
|
||||
case "play":
|
||||
media.play();
|
||||
break;
|
||||
case "pause":
|
||||
media.pause();
|
||||
break;
|
||||
case "mute":
|
||||
media.muted = true;
|
||||
break;
|
||||
case "unmute":
|
||||
media.muted = false;
|
||||
break;
|
||||
case "playbackRate":
|
||||
media.playbackRate = data;
|
||||
break;
|
||||
case "hidecontrols":
|
||||
media.removeAttribute("controls");
|
||||
break;
|
||||
case "showcontrols":
|
||||
media.setAttribute("controls", "true");
|
||||
break;
|
||||
case "hidestats":
|
||||
case "showstats":
|
||||
var event = media.ownerDocument.createEvent("CustomEvent");
|
||||
event.initCustomEvent("media-showStatistics", false, true, command == "showstats");
|
||||
media.dispatchEvent(event);
|
||||
break;
|
||||
}
|
||||
let mm = this.browser.messageManager;
|
||||
mm.sendAsyncMessage("ContextMenu:MediaCommand",
|
||||
{command: command, data: data},
|
||||
{element: this.target});
|
||||
},
|
||||
|
||||
copyMediaLocation : function () {
|
||||
|
|
|
@ -32,7 +32,8 @@ const HTML_NS = "http://www.w3.org/1999/xhtml";
|
|||
* @param onClick
|
||||
* A function that's called when a search suggestion is clicked. Ideally
|
||||
* we could call submit() on inputElement's ancestor form, but that
|
||||
* doesn't trigger submit listeners.
|
||||
* doesn't trigger submit listeners. The function is passed one argument,
|
||||
* the click event.
|
||||
* @param idPrefix
|
||||
* The IDs of elements created by the object will be prefixed with this
|
||||
* string.
|
||||
|
@ -234,6 +235,9 @@ SearchSuggestionUIController.prototype = {
|
|||
},
|
||||
|
||||
_onMousedown: function (event) {
|
||||
if (event.button == 2) {
|
||||
return;
|
||||
}
|
||||
let idx = this._indexOfTableRowOrDescendent(event.target);
|
||||
let suggestion = this.suggestionAtIndex(idx);
|
||||
this._stickyInputValue = suggestion;
|
||||
|
@ -251,7 +255,7 @@ SearchSuggestionUIController.prototype = {
|
|||
this.input.setAttribute("selection-kind", "mouse");
|
||||
this._hideSuggestions();
|
||||
if (this.onClick) {
|
||||
this.onClick.call(null);
|
||||
this.onClick.call(null, event);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -984,7 +984,7 @@
|
|||
<stylesheet src="chrome://browser/skin/searchbar.css"/>
|
||||
</resources>
|
||||
<content ignorekeys="true" level="top" consumeoutsideclicks="never">
|
||||
<xul:hbox xbl:inherits="collapsed=showonlysettings" anonid="searchbar-engine"
|
||||
<xul:hbox anonid="searchbar-engine" xbl:inherits="showonlysettings"
|
||||
class="search-panel-header search-panel-current-engine">
|
||||
<xul:image class="searchbar-engine-image" xbl:inherits="src"/>
|
||||
<xul:label anonid="searchbar-engine-name" flex="1" crop="end"
|
||||
|
@ -1000,8 +1000,7 @@
|
|||
</xul:tree>
|
||||
<xul:deck anonid="search-panel-one-offs-header"
|
||||
selectedIndex="0"
|
||||
class="search-panel-header search-panel-current-input"
|
||||
xbl:inherits="hidden=showonlysettings">
|
||||
class="search-panel-header search-panel-current-input">
|
||||
<xul:label anonid="searchbar-oneoffheader-search" value="&searchWithHeader.label;"/>
|
||||
<xul:hbox anonid="search-panel-searchforwith"
|
||||
class="search-panel-current-input">
|
||||
|
@ -1011,11 +1010,9 @@
|
|||
</xul:hbox>
|
||||
</xul:deck>
|
||||
<xul:description anonid="search-panel-one-offs"
|
||||
class="search-panel-one-offs"
|
||||
xbl:inherits="hidden=showonlysettings"/>
|
||||
class="search-panel-one-offs"/>
|
||||
<xul:vbox anonid="add-engines"/>
|
||||
<xul:button anonid="search-settings"
|
||||
xbl:inherits="showonlysettings"
|
||||
oncommand="BrowserUITelemetry.countSearchSettingsEvent('searchbar');openPreferences('paneSearch')"
|
||||
class="search-setting-button search-panel-header"
|
||||
label="&changeSearchSettings.button;"/>
|
||||
|
|
|
@ -10,6 +10,8 @@ this.EXPORTED_SYMBOLS = ["CustomizableWidgets"];
|
|||
Cu.import("resource:///modules/CustomizableUI.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
|
||||
"resource:///modules/BrowserUITelemetry.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
||||
"resource://gre/modules/PlacesUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils",
|
||||
|
@ -1012,6 +1014,7 @@ if (Services.prefs.getBoolPref("privacy.panicButton.enabled")) {
|
|||
this._ensureSanitizer();
|
||||
this._sanitizer.range = this._getSanitizeRange(doc);
|
||||
let group = doc.getElementById("PanelUI-panic-timeSpan");
|
||||
BrowserUITelemetry.countPanicEvent(group.selectedItem.id);
|
||||
group.selectedItem = doc.getElementById("PanelUI-panic-5min");
|
||||
let itemsToClear = [
|
||||
"cookies", "history", "openWindows", "formdata", "sessions", "cache", "downloads"
|
||||
|
|
|
@ -730,6 +730,11 @@ let MozLoopServiceInternal = {
|
|||
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
|
||||
gLocalizedStrings.set(string.key, string.value);
|
||||
}
|
||||
// Supply the strings from the branding bundle on a per-need basis.
|
||||
let brandBundle =
|
||||
Services.strings.createBundle("chrome://branding/locale/brand.properties");
|
||||
// Unfortunately the `brandShortName` string is used by Loop with a lowercase 'N'.
|
||||
gLocalizedStrings.set("brandShortname", brandBundle.GetStringFromName("brandShortName"));
|
||||
|
||||
return gLocalizedStrings;
|
||||
},
|
||||
|
|
|
@ -202,6 +202,8 @@ loop.shared.actions = (function() {
|
|||
* Used to start a screen share.
|
||||
*/
|
||||
StartScreenShare: Action.define("startScreenShare", {
|
||||
// The part of the screen to share, e.g. "window" or "browser".
|
||||
type: String
|
||||
}),
|
||||
|
||||
/**
|
||||
|
|
|
@ -89,14 +89,13 @@ loop.OTSdkDriver = (function() {
|
|||
/**
|
||||
* Initiates a screen sharing publisher.
|
||||
*/
|
||||
startScreenShare: function() {
|
||||
startScreenShare: function(actionData) {
|
||||
this.dispatcher.dispatch(new sharedActions.ScreenSharingState({
|
||||
state: SCREEN_SHARE_STATES.PENDING
|
||||
}));
|
||||
|
||||
var config = this._getCopyPublisherConfig();
|
||||
// This is temporary until we get a sharing type selector
|
||||
config.videoSource = "window";
|
||||
config.videoSource = actionData.type;
|
||||
|
||||
this.screenshare = this.sdk.initPublisher(this.getScreenShareElementFunc(),
|
||||
config);
|
||||
|
|
|
@ -143,14 +143,16 @@ loop.shared.utils = (function(mozL10n) {
|
|||
return;
|
||||
}
|
||||
navigator.mozLoop.composeEmail(
|
||||
mozL10n.get("share_email_subject4", {
|
||||
clientShortname: mozL10n.get("clientShortname2")
|
||||
mozL10n.get("share_email_subject5", {
|
||||
clientShortname2: mozL10n.get("clientShortname2")
|
||||
}),
|
||||
mozL10n.get("share_email_body4", {
|
||||
mozL10n.get("share_email_body5", {
|
||||
callUrl: callUrl,
|
||||
clientShortname: mozL10n.get("clientShortname2"),
|
||||
brandShortname: mozL10n.get("brandShortname"),
|
||||
clientShortname2: mozL10n.get("clientShortname2"),
|
||||
clientSuperShortname: mozL10n.get("clientSuperShortname"),
|
||||
learnMoreUrl: navigator.mozLoop.getLoopPref("learnMoreUrl")
|
||||
}),
|
||||
}).replace(/\r\n/g, "\n").replace(/\n/g, "\r\n"),
|
||||
recipient
|
||||
);
|
||||
}
|
||||
|
|
|
@ -102,8 +102,18 @@ loop.shared.views = (function(_, l10n) {
|
|||
}
|
||||
},
|
||||
|
||||
_startScreenShare: function(type) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({
|
||||
type: type
|
||||
}));
|
||||
},
|
||||
|
||||
_handleShareTabs: function() {
|
||||
this._startScreenShare("browser");
|
||||
},
|
||||
|
||||
_handleShareWindows: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({}));
|
||||
this._startScreenShare("window");
|
||||
},
|
||||
|
||||
_getTitle: function() {
|
||||
|
@ -143,6 +153,9 @@ loop.shared.views = (function(_, l10n) {
|
|||
isActive ? null : React.createElement("span", {className: "chevron"})
|
||||
),
|
||||
React.createElement("ul", {ref: "menu", className: dropdownMenuClasses},
|
||||
React.createElement("li", {onClick: this._handleShareTabs, className: "disabled"},
|
||||
l10n.get("share_tabs_button_title")
|
||||
),
|
||||
React.createElement("li", {onClick: this._handleShareWindows},
|
||||
l10n.get("share_windows_button_title")
|
||||
)
|
||||
|
|
|
@ -102,8 +102,18 @@ loop.shared.views = (function(_, l10n) {
|
|||
}
|
||||
},
|
||||
|
||||
_startScreenShare: function(type) {
|
||||
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({
|
||||
type: type
|
||||
}));
|
||||
},
|
||||
|
||||
_handleShareTabs: function() {
|
||||
this._startScreenShare("browser");
|
||||
},
|
||||
|
||||
_handleShareWindows: function() {
|
||||
this.props.dispatcher.dispatch(new sharedActions.StartScreenShare({}));
|
||||
this._startScreenShare("window");
|
||||
},
|
||||
|
||||
_getTitle: function() {
|
||||
|
@ -143,6 +153,9 @@ loop.shared.views = (function(_, l10n) {
|
|||
{isActive ? null : <span className="chevron"/>}
|
||||
</button>
|
||||
<ul ref="menu" className={dropdownMenuClasses}>
|
||||
<li onClick={this._handleShareTabs} className="disabled">
|
||||
{l10n.get("share_tabs_button_title")}
|
||||
</li>
|
||||
<li onClick={this._handleShareWindows}>
|
||||
{l10n.get("share_windows_button_title")}
|
||||
</li>
|
||||
|
|
|
@ -139,7 +139,9 @@ describe("loop.OTSdkDriver", function () {
|
|||
});
|
||||
|
||||
it("should dispatch a `ScreenSharingState` action", function() {
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare());
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare({
|
||||
type: "window"
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
|
@ -149,7 +151,9 @@ describe("loop.OTSdkDriver", function () {
|
|||
});
|
||||
|
||||
it("should initialize a publisher", function() {
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare());
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare({
|
||||
type: "window"
|
||||
}));
|
||||
|
||||
sinon.assert.calledOnce(sdk.initPublisher);
|
||||
sinon.assert.calledWithMatch(sdk.initPublisher,
|
||||
|
@ -161,7 +165,9 @@ describe("loop.OTSdkDriver", function () {
|
|||
beforeEach(function() {
|
||||
driver.getScreenShareElementFunc = function() {};
|
||||
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare());
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare({
|
||||
type: "window"
|
||||
}));
|
||||
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
|
||||
|
@ -611,7 +617,9 @@ describe("loop.OTSdkDriver", function () {
|
|||
|
||||
driver.getScreenShareElementFunc = function() {};
|
||||
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare());
|
||||
driver.startScreenShare(new sharedActions.StartScreenShare({
|
||||
type: "window"
|
||||
}));
|
||||
|
||||
sandbox.stub(dispatcher, "dispatch");
|
||||
});
|
||||
|
|
|
@ -152,8 +152,8 @@ describe("loop.shared.utils", function() {
|
|||
// fake mozL10n
|
||||
sandbox.stub(navigator.mozL10n, "get", function(id) {
|
||||
switch(id) {
|
||||
case "share_email_subject4": return "subject";
|
||||
case "share_email_body4": return "body";
|
||||
case "share_email_subject5": return "subject";
|
||||
case "share_email_body5": return "body";
|
||||
}
|
||||
});
|
||||
composeEmail = sandbox.spy();
|
||||
|
|
|
@ -152,7 +152,7 @@ describe("loop.shared.views", function() {
|
|||
expect(comp.state.showMenu).eql(true);
|
||||
});
|
||||
|
||||
it("should dispatch a StartScreenShare action on option click in screenshare dropdown",
|
||||
it("should dispatch a 'browser' StartScreenShare action on option click",
|
||||
function() {
|
||||
var comp = TestUtils.renderIntoDocument(
|
||||
React.createElement(sharedViews.ScreenShareControlButton, {
|
||||
|
@ -166,7 +166,24 @@ describe("loop.shared.views", function() {
|
|||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.StartScreenShare({}));
|
||||
new sharedActions.StartScreenShare({ type: "browser" }));
|
||||
});
|
||||
|
||||
it("should dispatch a 'window' StartScreenShare action on option click",
|
||||
function() {
|
||||
var comp = TestUtils.renderIntoDocument(
|
||||
React.createElement(sharedViews.ScreenShareControlButton, {
|
||||
dispatcher: dispatcher,
|
||||
visible: true,
|
||||
state: SCREEN_SHARE_STATES.INACTIVE
|
||||
}));
|
||||
|
||||
TestUtils.Simulate.click(comp.getDOMNode().querySelector(
|
||||
".conversation-window-dropdown > li:last-child"));
|
||||
|
||||
sinon.assert.calledOnce(dispatcher.dispatch);
|
||||
sinon.assert.calledWithExactly(dispatcher.dispatch,
|
||||
new sharedActions.StartScreenShare({ type: "window" }));
|
||||
});
|
||||
|
||||
it("should dispatch a EndScreenShare action on click when the state is active",
|
||||
|
|
|
@ -29,9 +29,6 @@ skip-if = e10s && (os == 'linux' || os == 'mac') # Bug 1116457
|
|||
skip-if = e10s
|
||||
|
||||
[browser_forgetthissite_single.js]
|
||||
# disabled for very frequent oranges - bug 551540
|
||||
skip-if = true
|
||||
|
||||
[browser_library_commands.js]
|
||||
[browser_drag_bookmarks_on_toolbar.js]
|
||||
skip-if = e10s # Bug ?????? - test fails - "Number of dragged items should be the same. - Got 0, expected 1"
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Test we correctly migrate Library left pane to the latest version.
|
||||
* Note: this test MUST be the first between browser chrome tests, or results
|
||||
|
@ -12,39 +14,7 @@
|
|||
|
||||
const TEST_URI = "http://www.mozilla.org/";
|
||||
|
||||
function onLibraryReady(organizer) {
|
||||
// Check left pane.
|
||||
ok(PlacesUIUtils.leftPaneFolderId > 0,
|
||||
"Left pane folder correctly created");
|
||||
var leftPaneItems =
|
||||
PlacesUtils.annotations
|
||||
.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(leftPaneItems.length, 1,
|
||||
"We correctly have only 1 left pane folder");
|
||||
var leftPaneRoot = leftPaneItems[0];
|
||||
is(leftPaneRoot, PlacesUIUtils.leftPaneFolderId,
|
||||
"leftPaneFolderId getter has correct value");
|
||||
// Check version has been upgraded.
|
||||
var version =
|
||||
PlacesUtils.annotations.getItemAnnotation(leftPaneRoot,
|
||||
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
|
||||
"Left pane version has been correctly upgraded");
|
||||
|
||||
// Check left pane is populated.
|
||||
organizer.PlacesOrganizer.selectLeftPaneQuery('History');
|
||||
is(organizer.PlacesOrganizer._places.selectedNode.itemId,
|
||||
PlacesUIUtils.leftPaneQueries["History"],
|
||||
"Library left pane is populated and working");
|
||||
|
||||
// Close Library window.
|
||||
organizer.close();
|
||||
// No need to cleanup anything, we have a correct left pane now.
|
||||
finish();
|
||||
}
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
add_task(function* () {
|
||||
// Sanity checks.
|
||||
ok(PlacesUtils, "PlacesUtils is running in chrome context");
|
||||
ok(PlacesUIUtils, "PlacesUIUtils is running in chrome context");
|
||||
|
@ -52,42 +22,69 @@ function test() {
|
|||
"Left pane version in chrome context, current version is: " + PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION );
|
||||
|
||||
// Check if we have any left pane folder already set, remove it eventually.
|
||||
var leftPaneItems = PlacesUtils.annotations
|
||||
let leftPaneItems = PlacesUtils.annotations
|
||||
.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
if (leftPaneItems.length > 0) {
|
||||
// The left pane has already been created, touching it now would cause
|
||||
// next tests to rely on wrong values (and possibly crash)
|
||||
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
|
||||
// Check version.
|
||||
var version = PlacesUtils.annotations.getItemAnnotation(leftPaneItems[0],
|
||||
let version = PlacesUtils.annotations.getItemAnnotation(leftPaneItems[0],
|
||||
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION, "Left pane version is actual");
|
||||
ok(true, "left pane has already been created, skipping test");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a fake left pane folder with an old version (current version - 1).
|
||||
var fakeLeftPaneRoot =
|
||||
PlacesUtils.bookmarks.createFolder(PlacesUtils.placesRootId, "",
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
PlacesUtils.annotations.setItemAnnotation(fakeLeftPaneRoot,
|
||||
let folder = yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.rootGuid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
title: ""
|
||||
});
|
||||
|
||||
let folderId = yield PlacesUtils.promiseItemId(folder.guid);
|
||||
PlacesUtils.annotations.setItemAnnotation(folderId,
|
||||
PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
|
||||
PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1,
|
||||
0,
|
||||
PlacesUtils.annotations.EXPIRE_NEVER);
|
||||
|
||||
// Check fake left pane root has been correctly created.
|
||||
var leftPaneItems =
|
||||
leftPaneItems =
|
||||
PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
|
||||
is(leftPaneItems[0], fakeLeftPaneRoot, "left pane root itemId is correct");
|
||||
is(leftPaneItems[0], folderId, "left pane root itemId is correct");
|
||||
|
||||
// Check version.
|
||||
var version = PlacesUtils.annotations.getItemAnnotation(fakeLeftPaneRoot,
|
||||
let version = PlacesUtils.annotations.getItemAnnotation(folderId,
|
||||
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION - 1, "Left pane version correctly set");
|
||||
|
||||
// Open Library, this will upgrade our left pane version.
|
||||
openLibrary(onLibraryReady);
|
||||
}
|
||||
let organizer = yield promiseLibrary();
|
||||
|
||||
// Check left pane.
|
||||
ok(PlacesUIUtils.leftPaneFolderId > 0, "Left pane folder correctly created");
|
||||
leftPaneItems =
|
||||
PlacesUtils.annotations.getItemsWithAnnotation(PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(leftPaneItems.length, 1, "We correctly have only 1 left pane folder");
|
||||
let leftPaneRoot = leftPaneItems[0];
|
||||
is(leftPaneRoot, PlacesUIUtils.leftPaneFolderId,
|
||||
"leftPaneFolderId getter has correct value");
|
||||
|
||||
// Check version has been upgraded.
|
||||
version = PlacesUtils.annotations.getItemAnnotation(leftPaneRoot,
|
||||
PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
|
||||
is(version, PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
|
||||
"Left pane version has been correctly upgraded");
|
||||
|
||||
// Check left pane is populated.
|
||||
organizer.PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
is(organizer.PlacesOrganizer._places.selectedNode.itemId,
|
||||
PlacesUIUtils.leftPaneQueries["History"],
|
||||
"Library left pane is populated and working");
|
||||
|
||||
yield promiseLibraryClosed(organizer);
|
||||
});
|
||||
|
|
|
@ -2,139 +2,113 @@
|
|||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
function add_bookmark(aURI) {
|
||||
return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"bookmark/" + aURI.spec);
|
||||
}
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
const TEST_URL = Services.io.newURI("http://example.com/", null, null);
|
||||
const MOZURISPEC = Services.io.newURI("http://mozilla.com/", null, null);
|
||||
|
||||
const TEST_URL = "http://example.com/";
|
||||
const MOZURISPEC = "http://mozilla.com/";
|
||||
add_task(function* () {
|
||||
let organizer = yield promiseLibrary();
|
||||
|
||||
let gLibrary;
|
||||
let PlacesOrganizer;
|
||||
let ContentTree;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gLibrary = openLibrary(onLibraryReady);
|
||||
}
|
||||
|
||||
function onLibraryReady() {
|
||||
ok(PlacesUtils, "PlacesUtils in scope");
|
||||
ok(PlacesUIUtils, "PlacesUIUtils in scope");
|
||||
|
||||
PlacesOrganizer = gLibrary.PlacesOrganizer;
|
||||
let PlacesOrganizer = organizer.PlacesOrganizer;
|
||||
ok(PlacesOrganizer, "Places organizer in scope");
|
||||
|
||||
ContentTree = gLibrary.ContentTree;
|
||||
let ContentTree = organizer.ContentTree;
|
||||
ok(ContentTree, "ContentTree is in scope");
|
||||
|
||||
tests.makeHistVisit(function() {
|
||||
tests.makeTag();
|
||||
tests.focusTag();
|
||||
waitForClipboard(function(aData) !!aData,
|
||||
tests.copyHistNode,
|
||||
onClipboardReady,
|
||||
PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
let visits = {uri: MOZURISPEC, transition: PlacesUtils.history.TRANSITION_TYPED};
|
||||
yield PlacesTestUtils.addVisits(visits);
|
||||
|
||||
// create an initial tag to work with
|
||||
let bm = yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
title: "bookmark/" + TEST_URL.spec,
|
||||
url: TEST_URL
|
||||
});
|
||||
}
|
||||
|
||||
function onClipboardReady() {
|
||||
tests.focusTag();
|
||||
ok(bm, "A bookmark was added");
|
||||
PlacesUtils.tagging.tagURI(TEST_URL, ["foo"]);
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(TEST_URL);
|
||||
is(tags[0], "foo", "tag is foo");
|
||||
|
||||
// focus the new tag
|
||||
focusTag(PlacesOrganizer);
|
||||
|
||||
let populate = () => copyHistNode(PlacesOrganizer, ContentTree);
|
||||
yield promiseClipboard(populate, PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
|
||||
focusTag(PlacesOrganizer);
|
||||
PlacesOrganizer._places.controller.paste();
|
||||
tests.historyNode();
|
||||
tests.checkForBookmarkInUI();
|
||||
|
||||
gLibrary.close();
|
||||
// re-focus the history again
|
||||
PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
let histContainer = PlacesOrganizer._places.selectedNode;
|
||||
PlacesUtils.asContainer(histContainer);
|
||||
histContainer.containerOpen = true;
|
||||
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
|
||||
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
|
||||
ok(histNode, "histNode exists: " + histNode.title);
|
||||
|
||||
// check to see if the history node is tagged!
|
||||
tags = PlacesUtils.tagging.getTagsForURI(MOZURISPEC);
|
||||
ok(tags.length == 1, "history node is tagged: " + tags.length);
|
||||
|
||||
// check if a bookmark was created
|
||||
let bookmarks = [];
|
||||
yield PlacesUtils.bookmarks.fetch({url: MOZURISPEC}, bm => {
|
||||
bookmarks.push(bm);
|
||||
});
|
||||
ok(bookmarks.length > 0, "bookmark exists for the tagged history item");
|
||||
|
||||
// is the bookmark visible in the UI?
|
||||
// get the Unsorted Bookmarks node
|
||||
PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
|
||||
|
||||
// now we can see what is in the ContentTree tree
|
||||
let unsortedNode = ContentTree.view.view.nodeForTreeIndex(1);
|
||||
ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri);
|
||||
is(unsortedNode.uri, MOZURISPEC.spec, "node uri's are the same");
|
||||
|
||||
yield promiseLibraryClosed(organizer);
|
||||
|
||||
// Remove new Places data we created.
|
||||
PlacesUtils.tagging.untagURI(NetUtil.newURI(MOZURISPEC), ["foo"]);
|
||||
PlacesUtils.tagging.untagURI(NetUtil.newURI(TEST_URL), ["foo"]);
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
|
||||
PlacesUtils.tagging.untagURI(MOZURISPEC, ["foo"]);
|
||||
PlacesUtils.tagging.untagURI(TEST_URL, ["foo"]);
|
||||
tags = PlacesUtils.tagging.getTagsForURI(TEST_URL);
|
||||
is(tags.length, 0, "tags are gone");
|
||||
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
|
||||
|
||||
PlacesTestUtils.clearHistory().then(finish);
|
||||
yield PlacesUtils.bookmarks.eraseEverything();
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
});
|
||||
|
||||
function focusTag(PlacesOrganizer) {
|
||||
PlacesOrganizer.selectLeftPaneQuery("Tags");
|
||||
let tags = PlacesOrganizer._places.selectedNode;
|
||||
tags.containerOpen = true;
|
||||
let fooTag = tags.getChild(0);
|
||||
let tagNode = fooTag;
|
||||
PlacesOrganizer._places.selectNode(fooTag);
|
||||
is(tagNode.title, 'foo', "tagNode title is foo");
|
||||
let ip = PlacesOrganizer._places.insertionPoint;
|
||||
ok(ip.isTag, "IP is a tag");
|
||||
}
|
||||
|
||||
let tests = {
|
||||
|
||||
makeHistVisit: function(aCallback) {
|
||||
// need to add a history object
|
||||
let testURI1 = NetUtil.newURI(MOZURISPEC);
|
||||
isnot(testURI1, null, "testURI is not null");
|
||||
PlacesTestUtils.addVisits(
|
||||
{uri: testURI1, transition: PlacesUtils.history.TRANSITION_TYPED}
|
||||
).then(aCallback);
|
||||
},
|
||||
|
||||
makeTag: function() {
|
||||
// create an initial tag to work with
|
||||
let bmId = add_bookmark(NetUtil.newURI(TEST_URL));
|
||||
ok(bmId > 0, "A bookmark was added");
|
||||
PlacesUtils.tagging.tagURI(NetUtil.newURI(TEST_URL), ["foo"]);
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
|
||||
is(tags[0], 'foo', "tag is foo");
|
||||
},
|
||||
|
||||
focusTag: function (){
|
||||
// focus the new tag
|
||||
PlacesOrganizer.selectLeftPaneQuery("Tags");
|
||||
let tags = PlacesOrganizer._places.selectedNode;
|
||||
tags.containerOpen = true;
|
||||
let fooTag = tags.getChild(0);
|
||||
let tagNode = fooTag;
|
||||
PlacesOrganizer._places.selectNode(fooTag);
|
||||
is(tagNode.title, 'foo', "tagNode title is foo");
|
||||
let ip = PlacesOrganizer._places.insertionPoint;
|
||||
ok(ip.isTag, "IP is a tag");
|
||||
},
|
||||
|
||||
copyHistNode: function (){
|
||||
// focus the history object
|
||||
PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
let histContainer = PlacesOrganizer._places.selectedNode;
|
||||
PlacesUtils.asContainer(histContainer);
|
||||
histContainer.containerOpen = true;
|
||||
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
|
||||
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
|
||||
ContentTree.view.selectNode(histNode);
|
||||
is(histNode.uri, MOZURISPEC,
|
||||
"historyNode exists: " + histNode.uri);
|
||||
// copy the history node
|
||||
ContentTree.view.controller.copy();
|
||||
},
|
||||
|
||||
historyNode: function (){
|
||||
// re-focus the history again
|
||||
PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
let histContainer = PlacesOrganizer._places.selectedNode;
|
||||
PlacesUtils.asContainer(histContainer);
|
||||
histContainer.containerOpen = true;
|
||||
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
|
||||
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
|
||||
ok(histNode, "histNode exists: " + histNode.title);
|
||||
// check to see if the history node is tagged!
|
||||
let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(MOZURISPEC));
|
||||
ok(tags.length == 1, "history node is tagged: " + tags.length);
|
||||
// check if a bookmark was created
|
||||
let isBookmarked = PlacesUtils.bookmarks.isBookmarked(NetUtil.newURI(MOZURISPEC));
|
||||
is(isBookmarked, true, MOZURISPEC + " is bookmarked");
|
||||
let bookmarkIds = PlacesUtils.bookmarks.getBookmarkIdsForURI(
|
||||
NetUtil.newURI(histNode.uri));
|
||||
ok(bookmarkIds.length > 0, "bookmark exists for the tagged history item: " + bookmarkIds);
|
||||
},
|
||||
|
||||
checkForBookmarkInUI: function(){
|
||||
// is the bookmark visible in the UI?
|
||||
// get the Unsorted Bookmarks node
|
||||
PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
|
||||
// now we can see what is in the ContentTree tree
|
||||
let unsortedNode = ContentTree.view.view.nodeForTreeIndex(1);
|
||||
ok(unsortedNode, "unsortedNode is not null: " + unsortedNode.uri);
|
||||
is(unsortedNode.uri, MOZURISPEC, "node uri's are the same");
|
||||
},
|
||||
};
|
||||
function copyHistNode(PlacesOrganizer, ContentTree) {
|
||||
// focus the history object
|
||||
PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
let histContainer = PlacesOrganizer._places.selectedNode;
|
||||
PlacesUtils.asContainer(histContainer);
|
||||
histContainer.containerOpen = true;
|
||||
PlacesOrganizer._places.selectNode(histContainer.getChild(0));
|
||||
let histNode = ContentTree.view.view.nodeForTreeIndex(0);
|
||||
ContentTree.view.selectNode(histNode);
|
||||
is(histNode.uri, MOZURISPEC.spec,
|
||||
"historyNode exists: " + histNode.uri);
|
||||
// copy the history node
|
||||
ContentTree.view.controller.copy();
|
||||
}
|
||||
|
|
|
@ -1,22 +1,14 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "http://example.com/";
|
||||
|
||||
let gLibrary;
|
||||
let gItemId;
|
||||
let PlacesOrganizer;
|
||||
let ContentTree;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
gLibrary = openLibrary(onLibraryReady);
|
||||
}
|
||||
|
||||
function onLibraryReady() {
|
||||
PlacesOrganizer = gLibrary.PlacesOrganizer;
|
||||
ContentTree = gLibrary.ContentTree;
|
||||
add_task(function* () {
|
||||
let organizer = yield promiseLibrary();
|
||||
let PlacesOrganizer = organizer.PlacesOrganizer;
|
||||
let ContentTree = organizer.ContentTree;
|
||||
|
||||
// Sanity checks.
|
||||
ok(PlacesUtils, "PlacesUtils in scope");
|
||||
|
@ -24,49 +16,44 @@ function onLibraryReady() {
|
|||
ok(PlacesOrganizer, "PlacesOrganizer in scope");
|
||||
ok(ContentTree, "ContentTree is in scope");
|
||||
|
||||
gItemId = PlacesUtils.bookmarks.insertBookmark(
|
||||
PlacesUtils.toolbarFolderId, NetUtil.newURI(TEST_URL),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX, "test"
|
||||
);
|
||||
let bm = yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
url: TEST_URL
|
||||
});
|
||||
|
||||
selectBookmarkIn("BookmarksToolbar");
|
||||
yield selectBookmarkIn(organizer, bm, "BookmarksToolbar");
|
||||
|
||||
waitForClipboard(function(aData) !!aData,
|
||||
cutSelection,
|
||||
onClipboardReady,
|
||||
PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
}
|
||||
yield promiseClipboard(() => {
|
||||
info("Cutting selection");
|
||||
ContentTree.view.controller.cut();
|
||||
}, PlacesUtils.TYPE_X_MOZ_PLACE);
|
||||
|
||||
info("Selecting UnfiledBookmarks in the left pane");
|
||||
PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
|
||||
info("Pasting clipboard");
|
||||
ContentTree.view.controller.paste();
|
||||
|
||||
yield selectBookmarkIn(organizer, bm, "UnfiledBookmarks");
|
||||
|
||||
yield promiseLibraryClosed(organizer);
|
||||
yield PlacesUtils.bookmarks.eraseEverything();
|
||||
});
|
||||
|
||||
let selectBookmarkIn = Task.async(function* (organizer, bm, aLeftPaneQuery) {
|
||||
let PlacesOrganizer = organizer.PlacesOrganizer;
|
||||
let ContentTree = organizer.ContentTree;
|
||||
|
||||
function selectBookmarkIn(aLeftPaneQuery) {
|
||||
info("Selecting " + aLeftPaneQuery + " in the left pane");
|
||||
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
|
||||
let rootId = PlacesUtils.getConcreteItemId(PlacesOrganizer._places.selectedNode);
|
||||
is(PlacesUtils.bookmarks.getFolderIdForItem(gItemId), rootId,
|
||||
|
||||
bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
|
||||
is((yield PlacesUtils.promiseItemId(bm.parentGuid)), rootId,
|
||||
"Bookmark has the right parent");
|
||||
|
||||
info("Selecting the bookmark in the right pane");
|
||||
ContentTree.view.selectItems([gItemId]);
|
||||
ContentTree.view.selectItems([yield PlacesUtils.promiseItemId(bm.guid)]);
|
||||
let bookmarkNode = ContentTree.view.selectedNode;
|
||||
is(bookmarkNode.uri, TEST_URL, "Found the expected bookmark");
|
||||
}
|
||||
|
||||
function cutSelection() {
|
||||
info("Cutting selection");
|
||||
ContentTree.view.controller.cut();
|
||||
}
|
||||
|
||||
function pasteClipboard(aLeftPaneQuery) {
|
||||
info("Selecting " + aLeftPaneQuery + " in the left pane");
|
||||
PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
|
||||
info("Pasting clipboard");
|
||||
ContentTree.view.controller.paste();
|
||||
}
|
||||
|
||||
function onClipboardReady() {
|
||||
pasteClipboard("UnfiledBookmarks");
|
||||
selectBookmarkIn("UnfiledBookmarks");
|
||||
|
||||
// Cleanup.
|
||||
gLibrary.close();
|
||||
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,54 +1,66 @@
|
|||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* 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/. */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function test() {
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
Deep copy of bookmark data, using the front-end codepath:
|
||||
|
||||
- create test folder A
|
||||
- add a subfolder to folder A, and add items to it
|
||||
- validate folder A (sanity check)
|
||||
- copy folder A, creating new folder B, using the front-end path
|
||||
- validate folder B
|
||||
- undo copy transaction
|
||||
- validate folder B (empty)
|
||||
- redo copy transaction
|
||||
- validate folder B's contents
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
// sanity check
|
||||
ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
|
||||
ok(PlacesUIUtils, "checking PlacesUIUtils, running in chrome context?");
|
||||
|
||||
/*
|
||||
Deep copy of bookmark data, using the front-end codepath:
|
||||
let toolbarId = PlacesUtils.toolbarFolderId;
|
||||
let toolbarNode = PlacesUtils.getFolderContents(toolbarId).root;
|
||||
|
||||
- create test folder A
|
||||
- add a subfolder to folder A, and add items to it
|
||||
- validate folder A (sanity check)
|
||||
- copy folder A, creating new folder B, using the front-end path
|
||||
- validate folder B
|
||||
- undo copy transaction
|
||||
- validate folder B (empty)
|
||||
- redo copy transaction
|
||||
- validate folder B's contents
|
||||
|
||||
*/
|
||||
|
||||
var toolbarId = PlacesUtils.toolbarFolderId;
|
||||
var toolbarNode = PlacesUtils.getFolderContents(toolbarId).root;
|
||||
|
||||
var oldCount = toolbarNode.childCount;
|
||||
var testRootId = PlacesUtils.bookmarks.createFolder(toolbarId, "test root", -1);
|
||||
let oldCount = toolbarNode.childCount;
|
||||
let testRoot = yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
title: "test root"
|
||||
});
|
||||
is(toolbarNode.childCount, oldCount+1, "confirm test root node is a container, and is empty");
|
||||
var testRootNode = toolbarNode.getChild(toolbarNode.childCount-1);
|
||||
|
||||
let testRootNode = toolbarNode.getChild(toolbarNode.childCount-1);
|
||||
testRootNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
testRootNode.containerOpen = true;
|
||||
is(testRootNode.childCount, 0, "confirm test root node is a container, and is empty");
|
||||
|
||||
// create folder A, fill it, validate its contents
|
||||
var folderAId = PlacesUtils.bookmarks.createFolder(testRootId, "A", -1);
|
||||
populate(folderAId);
|
||||
var folderANode = PlacesUtils.getFolderContents(folderAId).root;
|
||||
let folderA = yield PlacesUtils.bookmarks.insert({
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: testRoot.guid,
|
||||
title: "A"
|
||||
});
|
||||
|
||||
yield populate(folderA);
|
||||
|
||||
let folderAId = yield PlacesUtils.promiseItemId(folderA.guid);
|
||||
let folderANode = PlacesUtils.getFolderContents(folderAId).root;
|
||||
validate(folderANode);
|
||||
is(testRootNode.childCount, 1, "create test folder");
|
||||
|
||||
// copy it, using the front-end helper functions
|
||||
var serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
|
||||
var rawNode = PlacesUtils.unwrapNodes(serializedNode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift();
|
||||
let serializedNode = PlacesUtils.wrapNode(folderANode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER);
|
||||
let rawNode = PlacesUtils.unwrapNodes(serializedNode, PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER).shift();
|
||||
// confirm serialization
|
||||
ok(rawNode.type, "confirm json node");
|
||||
folderANode.containerOpen = false;
|
||||
|
||||
var transaction = PlacesUIUtils.makeTransaction(rawNode,
|
||||
let testRootId = yield PlacesUtils.promiseItemId(testRoot.guid);
|
||||
let transaction = PlacesUIUtils.makeTransaction(rawNode,
|
||||
PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
|
||||
testRootId,
|
||||
-1,
|
||||
|
@ -59,7 +71,7 @@ function test() {
|
|||
is(testRootNode.childCount, 2, "create test folder via copy");
|
||||
|
||||
// validate the copy
|
||||
var folderBNode = testRootNode.getChild(1);
|
||||
let folderBNode = testRootNode.getChild(1);
|
||||
validate(folderBNode);
|
||||
|
||||
// undo the transaction, confirm the removal
|
||||
|
@ -78,14 +90,28 @@ function test() {
|
|||
|
||||
// clean up
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
PlacesUtils.bookmarks.removeItem(folderAId);
|
||||
}
|
||||
yield PlacesUtils.bookmarks.remove(folderA.guid);
|
||||
});
|
||||
|
||||
function populate(aFolderId) {
|
||||
var folderId = PlacesUtils.bookmarks.createFolder(aFolderId, "test folder", -1);
|
||||
PlacesUtils.bookmarks.insertBookmark(folderId, PlacesUtils._uri("http://foo"), -1, "test bookmark");
|
||||
PlacesUtils.bookmarks.insertSeparator(folderId, -1);
|
||||
}
|
||||
let populate = Task.async(function* (parentFolder) {
|
||||
let folder = yield PlacesUtils.bookmarks.insert({
|
||||
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||
parentGuid: parentFolder.guid,
|
||||
title: "test folder"
|
||||
});
|
||||
|
||||
yield PlacesUtils.bookmarks.insert({
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: folder.guid,
|
||||
title: "test bookmark",
|
||||
url: "http://foo"
|
||||
});
|
||||
|
||||
yield PlacesUtils.bookmarks.insert({
|
||||
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
|
||||
parentGuid: folder.guid
|
||||
});
|
||||
});
|
||||
|
||||
function validate(aNode) {
|
||||
PlacesUtils.asContainer(aNode);
|
||||
|
|
|
@ -2,69 +2,77 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URIs = [
|
||||
"http://www.mozilla.org/test1",
|
||||
"http://www.mozilla.org/test2"
|
||||
];
|
||||
|
||||
// This test makes sure that the Forget This Site command is hidden for multiple
|
||||
// selections.
|
||||
function test() {
|
||||
// initialization
|
||||
waitForExplicitFinish();
|
||||
|
||||
add_task(function* () {
|
||||
// Add a history entry.
|
||||
let TEST_URIs = ["http://www.mozilla.org/test1", "http://www.mozilla.org/test2"];
|
||||
ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
|
||||
let places = [];
|
||||
TEST_URIs.forEach(function(TEST_URI) {
|
||||
places.push({uri: PlacesUtils._uri(TEST_URI),
|
||||
transition: PlacesUtils.history.TRANSITION_TYPED});
|
||||
});
|
||||
PlacesTestUtils.addVisits(places).then(() => {
|
||||
testForgetThisSiteVisibility(1, function() {
|
||||
testForgetThisSiteVisibility(2, function() {
|
||||
// Cleanup
|
||||
PlacesTestUtils.clearHistory().then(finish);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function testForgetThisSiteVisibility(selectionCount, funcNext) {
|
||||
openLibrary(function (organizer) {
|
||||
// Select History in the left pane.
|
||||
organizer.PlacesOrganizer.selectLeftPaneQuery('History');
|
||||
let PO = organizer.PlacesOrganizer;
|
||||
let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
histContainer.containerOpen = true;
|
||||
PO._places.selectNode(histContainer.getChild(0));
|
||||
// Select the first history entry.
|
||||
let doc = organizer.document;
|
||||
let tree = PO._content;
|
||||
let selection = tree.view.selection;
|
||||
selection.clearSelection();
|
||||
selection.rangedSelect(0, selectionCount - 1, true);
|
||||
is(selection.count, selectionCount,
|
||||
"The selected range is as big as expected");
|
||||
// Open the context menu
|
||||
let contextmenu = doc.getElementById("placesContext");
|
||||
contextmenu.addEventListener("popupshown", function() {
|
||||
contextmenu.removeEventListener("popupshown", arguments.callee, true);
|
||||
let forgetThisSite = doc.getElementById("placesContext_deleteHost");
|
||||
let hideForgetThisSite = (selectionCount != 1);
|
||||
is(forgetThisSite.hidden, hideForgetThisSite,
|
||||
"The Forget this site menu item should " + (hideForgetThisSite ? "" : "not ") +
|
||||
"be hidden with " + selectionCount + " items selected");
|
||||
// Close the context menu
|
||||
contextmenu.hidePopup();
|
||||
// Wait for the Organizer window to actually be closed
|
||||
organizer.addEventListener("unload", function () {
|
||||
organizer.removeEventListener("unload", arguments.callee, false);
|
||||
// Proceed
|
||||
funcNext();
|
||||
}, false);
|
||||
// Close Library window.
|
||||
organizer.close();
|
||||
}, true);
|
||||
// Get cell coordinates
|
||||
var rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text");
|
||||
// Initiate a context menu for the selected cell
|
||||
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {type: "contextmenu"}, organizer);
|
||||
});
|
||||
}
|
||||
let places = [];
|
||||
let transition = PlacesUtils.history.TRANSITION_TYPED;
|
||||
TEST_URIs.forEach(uri => places.push({uri: PlacesUtils._uri(uri), transition}));
|
||||
|
||||
yield PlacesTestUtils.addVisits(places);
|
||||
yield testForgetThisSiteVisibility(1);
|
||||
yield testForgetThisSiteVisibility(2);
|
||||
|
||||
// Cleanup.
|
||||
yield PlacesTestUtils.clearHistory();
|
||||
});
|
||||
|
||||
let testForgetThisSiteVisibility = Task.async(function* (selectionCount) {
|
||||
let organizer = yield promiseLibrary();
|
||||
|
||||
// Select History in the left pane.
|
||||
organizer.PlacesOrganizer.selectLeftPaneQuery("History");
|
||||
let PO = organizer.PlacesOrganizer;
|
||||
let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
histContainer.containerOpen = true;
|
||||
PO._places.selectNode(histContainer.getChild(0));
|
||||
|
||||
// Select the first history entry.
|
||||
let doc = organizer.document;
|
||||
let tree = doc.getElementById("placeContent");
|
||||
let selection = tree.view.selection;
|
||||
selection.clearSelection();
|
||||
selection.rangedSelect(0, selectionCount - 1, true);
|
||||
is(selection.count, selectionCount, "The selected range is as big as expected");
|
||||
|
||||
// Open the context menu.
|
||||
let contextmenu = doc.getElementById("placesContext");
|
||||
let popupShown = promisePopupShown(contextmenu);
|
||||
|
||||
// Get cell coordinates.
|
||||
let rect = tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text");
|
||||
// Initiate a context menu for the selected cell.
|
||||
EventUtils.synthesizeMouse(tree.body, rect.x + rect.width / 2, rect.y + rect.height / 2, {type: "contextmenu", button: 2}, organizer);
|
||||
yield popupShown;
|
||||
|
||||
let forgetThisSite = doc.getElementById("placesContext_deleteHost");
|
||||
let hideForgetThisSite = (selectionCount != 1);
|
||||
is(forgetThisSite.hidden, hideForgetThisSite,
|
||||
`The Forget this site menu item should ${hideForgetThisSite ? "" : "not "}` +
|
||||
` be hidden with ${selectionCount} items selected`);
|
||||
|
||||
// Close the context menu.
|
||||
contextmenu.hidePopup();
|
||||
|
||||
// Close the library window.
|
||||
yield promiseLibraryClosed(organizer);
|
||||
});
|
||||
|
||||
function promisePopupShown(popup) {
|
||||
return new Promise(resolve => {
|
||||
popup.addEventListener("popupshown", function onShown() {
|
||||
popup.removeEventListener("popupshown", onShown, true);
|
||||
resolve();
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -43,17 +43,31 @@ function openLibrary(callback, aLeftPaneRoot) {
|
|||
* Hierarchy to open and select in the left pane.
|
||||
*/
|
||||
function promiseLibrary(aLeftPaneRoot) {
|
||||
let deferred = Promise.defer();
|
||||
let library = Services.wm.getMostRecentWindow("Places:Organizer");
|
||||
if (library && !library.closed) {
|
||||
if (aLeftPaneRoot)
|
||||
library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
|
||||
deferred.resolve(library);
|
||||
}
|
||||
else {
|
||||
openLibrary(aLibrary => deferred.resolve(aLibrary), aLeftPaneRoot);
|
||||
}
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => {
|
||||
let library = Services.wm.getMostRecentWindow("Places:Organizer");
|
||||
if (library && !library.closed) {
|
||||
if (aLeftPaneRoot) {
|
||||
library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(aLeftPaneRoot);
|
||||
}
|
||||
resolve(library);
|
||||
}
|
||||
else {
|
||||
openLibrary(resolve, aLeftPaneRoot);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function promiseLibraryClosed(organizer) {
|
||||
return new Promise(resolve => {
|
||||
// Wait for the Organizer window to actually be closed
|
||||
organizer.addEventListener("unload", function onUnload() {
|
||||
organizer.removeEventListener("unload", onUnload);
|
||||
resolve();
|
||||
});
|
||||
|
||||
// Close Library window.
|
||||
organizer.close();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -67,12 +81,9 @@ function promiseLibrary(aLeftPaneRoot) {
|
|||
* Data flavor to expect.
|
||||
*/
|
||||
function promiseClipboard(aPopulateClipboardFn, aFlavor) {
|
||||
let deferred = Promise.defer();
|
||||
waitForClipboard(function (aData) !!aData,
|
||||
aPopulateClipboardFn,
|
||||
function () { deferred.resolve(); },
|
||||
aFlavor);
|
||||
return deferred.promise;
|
||||
return new Promise(resolve => {
|
||||
waitForClipboard(data => !!data, aPopulateClipboardFn, resolve, aFlavor);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -108,6 +108,37 @@ var gFontsDialog = {
|
|||
{
|
||||
var useDocumentFonts = document.getElementById("useDocumentFonts");
|
||||
return useDocumentFonts.checked ? 1 : 0;
|
||||
}
|
||||
},
|
||||
|
||||
onBeforeAccept: function ()
|
||||
{
|
||||
// Only care in in-content prefs
|
||||
if (!window.frameElement) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let preferences = document.querySelectorAll("preference[id*='font.minimum-size']");
|
||||
// It would be good if we could avoid touching languages the pref pages won't use, but
|
||||
// unfortunately the language group APIs (deducing language groups from language codes)
|
||||
// are C++ - only. So we just check all the things the user touched:
|
||||
// Don't care about anything up to 24px, or if this value is the same as set previously:
|
||||
preferences = Array.filter(preferences, prefEl => {
|
||||
return prefEl.value > 24 && prefEl.value != prefEl.valueFromPreferences;
|
||||
});
|
||||
if (!preferences.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let strings = document.getElementById("bundlePreferences");
|
||||
let title = strings.getString("veryLargeMinimumFontTitle");
|
||||
let confirmLabel = strings.getString("acceptVeryLargeMinimumFont");
|
||||
let warningMessage = strings.getString("veryLargeMinimumFontWarning");
|
||||
let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
|
||||
let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL |
|
||||
Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING |
|
||||
Services.prompt.BUTTON_POS_1_DEFAULT;
|
||||
let buttonChosen = Services.prompt.confirmEx(window, title, warningMessage, flags, confirmLabel, null, "", "", {});
|
||||
return buttonChosen == 0;
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
title="&fontsDialog.title;"
|
||||
dlgbuttons="accept,cancel,help"
|
||||
ondialoghelp="openPrefsHelp()"
|
||||
onbeforeaccept="return gFontsDialog.onBeforeAccept();"
|
||||
style="">
|
||||
|
||||
<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
|
||||
|
|
|
@ -1063,6 +1063,12 @@
|
|||
let selectedButton = this.selectedButton;
|
||||
let buttons = this.getSelectableButtons(aCycleEngines);
|
||||
|
||||
let suggestionsHidden;
|
||||
if (!aSkipSuggestions && popup.hasAttribute("showonlysettings")) {
|
||||
aSkipSuggestions = true;
|
||||
suggestionsHidden = true;
|
||||
}
|
||||
|
||||
// If the last suggestion is selected, DOWN selects the first button.
|
||||
if (!aSkipSuggestions && aForward &&
|
||||
popup.selectedIndex + 1 == popup.view.rowCount) {
|
||||
|
@ -1083,7 +1089,7 @@
|
|||
else
|
||||
this.selectedButton = null;
|
||||
|
||||
if (this.selectedButton || aCycleEngines)
|
||||
if (this.selectedButton || aCycleEngines || suggestionsHidden)
|
||||
return true;
|
||||
|
||||
// Set the selectedIndex to something that will make
|
||||
|
@ -1104,7 +1110,7 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
if (!aForward && (aCycleEngines ||
|
||||
if (!aForward && (aCycleEngines || suggestionsHidden ||
|
||||
(!aSkipSuggestions && popup.selectedIndex == -1))) {
|
||||
// the last button.
|
||||
this.selectedButton = buttons[buttons.length - 1];
|
||||
|
@ -1123,15 +1129,6 @@
|
|||
if (!popup.popupOpen)
|
||||
return;
|
||||
|
||||
if (aEvent.keyCode == KeyEvent.DOM_VK_DOWN &&
|
||||
popup.hasAttribute("showonlysettings")) {
|
||||
// If the suggestion tree is collapsed, don't let the down arrow
|
||||
// key event reach the tree.
|
||||
aEvent.preventDefault();
|
||||
aEvent.stopPropagation();
|
||||
return;
|
||||
}
|
||||
|
||||
let list = document.getAnonymousElementByAttribute(popup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
if (!list) // remove this check when removing the old search UI.
|
||||
|
|
|
@ -44,4 +44,5 @@ skip-if = e10s || true # Bug ??????, Bug 1100301 - leaks windows until shutdown
|
|||
[browser_searchbar_openpopup.js]
|
||||
skip-if = os == "linux" || e10s # Linux has different focus behaviours and e10s seems to have timing issues.
|
||||
[browser_searchbar_keyboard_navigation.js]
|
||||
[browser_searchbar_smallpanel_keyboard_navigation.js]
|
||||
[browser_webapi.js]
|
||||
|
|
|
@ -191,6 +191,27 @@ add_task(function* focus_change_closes_popup() {
|
|||
textbox.value = "";
|
||||
});
|
||||
|
||||
// Moving focus away from the search box should close the small popup
|
||||
add_task(function* focus_change_closes_small_popup() {
|
||||
gURLBar.focus();
|
||||
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
// For some reason sending the mouse event immediately doesn't open the popup.
|
||||
SimpleTest.executeSoon(() => {
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
});
|
||||
yield promise;
|
||||
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
|
||||
|
||||
is(Services.focus.focusedElement, textbox.inputField, "Should have focused the search bar");
|
||||
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
let promise2 = promiseEvent(searchbar, "blur");
|
||||
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
|
||||
yield promise;
|
||||
yield promise2;
|
||||
});
|
||||
|
||||
// Pressing escape should close the popup.
|
||||
add_task(function* escape_closes_popup() {
|
||||
gURLBar.focus();
|
||||
|
@ -442,4 +463,6 @@ add_task(function* dont_rollup_oncaretmove() {
|
|||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_ESCAPE", {});
|
||||
yield promise;
|
||||
|
||||
textbox.value = "";
|
||||
});
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
// Tests that keyboard navigation in the search panel works as designed.
|
||||
|
||||
const searchbar = document.getElementById("searchbar");
|
||||
const textbox = searchbar._textbox;
|
||||
const searchPopup = document.getElementById("PopupSearchAutoComplete");
|
||||
const searchIcon = document.getAnonymousElementByAttribute(searchbar, "anonid",
|
||||
"searchbar-search-button");
|
||||
|
||||
const kValues = ["foo1", "foo2", "foo3"];
|
||||
|
||||
// Get an array of the one-off buttons.
|
||||
function getOneOffs() {
|
||||
let oneOffs = [];
|
||||
let oneOff = document.getAnonymousElementByAttribute(searchPopup, "anonid",
|
||||
"search-panel-one-offs");
|
||||
for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
|
||||
if (oneOff.classList.contains("dummy"))
|
||||
break;
|
||||
oneOffs.push(oneOff);
|
||||
}
|
||||
|
||||
return oneOffs;
|
||||
}
|
||||
|
||||
function getOpenSearchItems() {
|
||||
let os = [];
|
||||
|
||||
let addEngineList =
|
||||
document.getAnonymousElementByAttribute(searchPopup, "anonid",
|
||||
"add-engines");
|
||||
for (let item = addEngineList.firstChild; item; item = item.nextSibling)
|
||||
os.push(item);
|
||||
|
||||
return os;
|
||||
}
|
||||
|
||||
add_task(function* init() {
|
||||
yield promiseNewEngine("testEngine.xml");
|
||||
|
||||
// First cleanup the form history in case other tests left things there.
|
||||
yield new Promise((resolve, reject) => {
|
||||
info("cleanup the search history");
|
||||
searchbar.FormHistory.update({op: "remove", fieldname: "searchbar-history"},
|
||||
{handleCompletion: resolve,
|
||||
handleError: reject});
|
||||
});
|
||||
|
||||
yield new Promise((resolve, reject) => {
|
||||
info("adding search history values: " + kValues);
|
||||
let ops = kValues.map(value => { return {op: "add",
|
||||
fieldname: "searchbar-history",
|
||||
value: value}
|
||||
});
|
||||
searchbar.FormHistory.update(ops, {
|
||||
handleCompletion: function() {
|
||||
registerCleanupFunction(() => {
|
||||
info("removing search history values: " + kValues);
|
||||
let ops =
|
||||
kValues.map(value => { return {op: "remove",
|
||||
fieldname: "searchbar-history",
|
||||
value: value}
|
||||
});
|
||||
searchbar.FormHistory.update(ops);
|
||||
});
|
||||
resolve();
|
||||
},
|
||||
handleError: reject
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
add_task(function* test_arrows() {
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
yield promise;
|
||||
info("textbox.mController.searchString = " + textbox.mController.searchString);
|
||||
is(textbox.mController.searchString, "", "The search string should be empty");
|
||||
|
||||
// Check the initial state of the panel before sending keyboard events.
|
||||
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
|
||||
// Having suggestions populated (but hidden) is important, because if there
|
||||
// are none we can't ensure the keyboard events don't reach them.
|
||||
is(searchPopup.view.rowCount, kValues.length, "There should be 3 suggestions");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
|
||||
// The tests will be less meaningful if the first, second, last, and
|
||||
// before-last one-off buttons aren't different. We should always have more
|
||||
// than 4 default engines, but it's safer to check this assumption.
|
||||
let oneOffs = getOneOffs();
|
||||
ok(oneOffs.length >= 4, "we have at least 4 one-off buttons displayed")
|
||||
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// Pressing should select the first one-off.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
|
||||
// now cycle through the one-off items, the first one is already selected.
|
||||
for (let i = 0; i < oneOffs.length; ++i) {
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
}
|
||||
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
|
||||
// We should now be back to the initial situation.
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
info("now test the up arrow key");
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// cycle through the one-off items, the first one is already selected.
|
||||
for (let i = oneOffs.length; i; --i) {
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton, oneOffs[i - 1],
|
||||
"the one-off button #" + i + " should be selected");
|
||||
}
|
||||
|
||||
// Another press on up should clear the one-off selection.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
});
|
||||
|
||||
add_task(function* test_tab() {
|
||||
is(Services.focus.focusedElement, textbox.inputField,
|
||||
"the search bar should be focused"); // from the previous test.
|
||||
|
||||
let oneOffs = getOneOffs();
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// Pressing tab should select the first one-off without selecting suggestions.
|
||||
// now cycle through the one-off items, the first one is already selected.
|
||||
for (let i = 0; i < oneOffs.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
}
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
|
||||
// One more <tab> selects the settings button.
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// Pressing tab again should close the panel...
|
||||
let promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
yield promise;
|
||||
|
||||
// ... and move the focus out of the searchbox.
|
||||
isnot(Services.focus.focusedElement, textbox.inputField,
|
||||
"the search bar no longer be focused");
|
||||
});
|
||||
|
||||
add_task(function* test_shift_tab() {
|
||||
// First reopen the panel.
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
SimpleTest.executeSoon(() => {
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
});
|
||||
yield promise;
|
||||
|
||||
let oneOffs = getOneOffs();
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
|
||||
|
||||
// Press up once to select the last button.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// Press up again to select the last one-off button.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
|
||||
// Pressing shift+tab should cycle through the one-off items.
|
||||
for (let i = oneOffs.length - 1; i >= 0; --i) {
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
if (i)
|
||||
EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
|
||||
}
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
|
||||
// Pressing shift+tab again should close the panel...
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
|
||||
yield promise;
|
||||
|
||||
// ... and move the focus out of the searchbox.
|
||||
isnot(Services.focus.focusedElement, textbox.inputField,
|
||||
"the search bar no longer be focused");
|
||||
});
|
||||
|
||||
add_task(function* test_alt_down() {
|
||||
// First reopen the panel.
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
SimpleTest.executeSoon(() => {
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
});
|
||||
yield promise;
|
||||
|
||||
// and check it's in a correct initial state.
|
||||
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
|
||||
// Pressing alt+down should select the first one-off without selecting suggestions
|
||||
// and cycle through the one-off items.
|
||||
let oneOffs = getOneOffs();
|
||||
for (let i = 0; i < oneOffs.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
}
|
||||
|
||||
// One more alt+down keypress and nothing should be selected.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// another one and the first one-off should be selected.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should be selected");
|
||||
|
||||
// Clear the selection with an alt+up keypress
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
});
|
||||
|
||||
add_task(function* test_alt_up() {
|
||||
// Check the initial state of the panel
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
|
||||
// Pressing alt+up should select the last one-off without selecting suggestions
|
||||
// and cycle up through the one-off items.
|
||||
let oneOffs = getOneOffs();
|
||||
for (let i = oneOffs.length - 1; i >= 0; --i) {
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[i],
|
||||
"the one-off button #" + (i + 1) + " should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
}
|
||||
|
||||
// One more alt+down keypress and nothing should be selected.
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
|
||||
// another one and the last one-off should be selected.
|
||||
EventUtils.synthesizeKey("VK_UP", {altKey: true});
|
||||
is(textbox.selectedButton, oneOffs[oneOffs.length - 1],
|
||||
"the last one-off button should be selected");
|
||||
|
||||
// Cleanup for the next test.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
ok(!textbox.selectedButton, "no one-off should be selected anymore");
|
||||
});
|
||||
|
||||
add_task(function* test_tab_and_arrows() {
|
||||
// Check the initial state is as expected.
|
||||
ok(!textbox.selectedButton, "no one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
is(textbox.value, "", "the textfield value should be unmodified");
|
||||
|
||||
// After pressing down, the first one-off should be selected.
|
||||
let oneOffs = getOneOffs();
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
|
||||
// After pressing tab, the second one-off should be selected.
|
||||
EventUtils.synthesizeKey("VK_TAB", {});
|
||||
is(textbox.selectedButton, oneOffs[1],
|
||||
"the second one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
|
||||
// After pressing up, the first one-off should be selected again.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton, oneOffs[0],
|
||||
"the first one-off button should be selected");
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
|
||||
// Finally close the panel.
|
||||
let promise = promiseEvent(searchPopup, "popuphidden");
|
||||
searchPopup.hidePopup();
|
||||
yield promise;
|
||||
});
|
||||
|
||||
add_task(function* test_open_search() {
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let browser = gBrowser.selectedBrowser;
|
||||
browser.addEventListener("load", function onload() {
|
||||
browser.removeEventListener("load", onload, true);
|
||||
deferred.resolve();
|
||||
}, true);
|
||||
|
||||
let rootDir = getRootDirectory(gTestPath);
|
||||
content.location = rootDir + "opensearch.html";
|
||||
|
||||
yield deferred.promise;
|
||||
|
||||
let promise = promiseEvent(searchPopup, "popupshown");
|
||||
info("Opening search panel");
|
||||
EventUtils.synthesizeMouseAtCenter(searchIcon, {});
|
||||
yield promise;
|
||||
is(searchPopup.getAttribute("showonlysettings"), "true", "Should show the small popup");
|
||||
|
||||
let engines = getOpenSearchItems();
|
||||
is(engines.length, 2, "the opensearch.html page exposes 2 engines")
|
||||
|
||||
// Check that there's initially no selection.
|
||||
is(searchPopup.selectedIndex, -1, "no suggestion should be selected");
|
||||
ok(!textbox.selectedButton, "no button should be selected");
|
||||
|
||||
// Pressing up once selects the setting button...
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
// ...and then pressing up selects open search engines.
|
||||
for (let i = engines.length; i; --i) {
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
let selectedButton = textbox.selectedButton;
|
||||
is(selectedButton, engines[i - 1],
|
||||
"the engine #" + i + " should be selected");
|
||||
ok(selectedButton.classList.contains("addengine-item"),
|
||||
"the button is themed as an engine item");
|
||||
}
|
||||
|
||||
// Pressing up again should select the last one-off button.
|
||||
EventUtils.synthesizeKey("VK_UP", {});
|
||||
is(textbox.selectedButton, getOneOffs().pop(),
|
||||
"the last one-off button should be selected");
|
||||
|
||||
info("now check that the down key navigates open search items as expected");
|
||||
for (let i = 0; i < engines.length; ++i) {
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton, engines[i],
|
||||
"the engine #" + (i + 1) + " should be selected");
|
||||
}
|
||||
|
||||
// Pressing down on the last engine item selects the settings button.
|
||||
EventUtils.synthesizeKey("VK_DOWN", {});
|
||||
is(textbox.selectedButton.getAttribute("anonid"), "search-settings",
|
||||
"the settings item should be selected");
|
||||
|
||||
promise = promiseEvent(searchPopup, "popuphidden");
|
||||
searchPopup.hidePopup();
|
||||
yield promise;
|
||||
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
|
@ -136,9 +136,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
|||
* - staged: true to stage the item to be appended later
|
||||
*/
|
||||
addSource: function(aSource, aOptions = {}) {
|
||||
if (!(aSource.url || aSource.introductionUrl)) {
|
||||
// These would be most likely eval scripts introduced in inline
|
||||
// JavaScript in HTML, and we don't show those yet (bug 1097873)
|
||||
if (!aSource.url) {
|
||||
// We don't show any unnamed eval scripts yet (see bug 1124106)
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -170,21 +169,10 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
|||
},
|
||||
|
||||
_parseUrl: function(aSource) {
|
||||
let fullUrl = aSource.url || aSource.introductionUrl;
|
||||
let fullUrl = aSource.url;
|
||||
let url = fullUrl.split(" -> ").pop();
|
||||
let label = aSource.addonPath ? aSource.addonPath : SourceUtils.getSourceLabel(url);
|
||||
let group;
|
||||
|
||||
if (!aSource.url && aSource.introductionUrl) {
|
||||
label += ' > ' + aSource.introductionType;
|
||||
group = L10N.getStr("evalGroupLabel");
|
||||
}
|
||||
else if(aSource.addonID) {
|
||||
group = aSource.addonID;
|
||||
}
|
||||
else {
|
||||
group = SourceUtils.getSourceGroup(url);
|
||||
}
|
||||
let group = aSource.addonID ? aSource.addonID : SourceUtils.getSourceGroup(url);
|
||||
|
||||
return {
|
||||
label: label,
|
||||
|
|
|
@ -407,7 +407,7 @@ skip-if = e10s # Bug 1093535
|
|||
[browser_dbg_sources-cache.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_sources-eval-01.js]
|
||||
skip-if = e10s && debug
|
||||
skip-if = true # non-named eval sources turned off for now, bug 1124106
|
||||
[browser_dbg_sources-eval-02.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_sources-labels.js]
|
||||
|
|
|
@ -28,16 +28,16 @@ function test() {
|
|||
function run() {
|
||||
return Task.spawn(function*() {
|
||||
let newSource = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.NEW_SOURCE);
|
||||
callInTab(gTab, "evalSource");
|
||||
callInTab(gTab, "evalSourceWithSourceURL");
|
||||
yield newSource;
|
||||
|
||||
yield gPanel.addBreakpoint({ actor: gSources.values[1], line: 2 });
|
||||
yield gPanel.addBreakpoint({ actor: gSources.values[0], line: 2 });
|
||||
yield ensureThreadClientState(gPanel, "resumed");
|
||||
|
||||
const paused = waitForThreadEvents(gPanel, "paused");
|
||||
callInTab(gTab, "bar");
|
||||
let frame = (yield paused).frame;
|
||||
is(frame.where.source.actor, gSources.values[1], "Should have broken on the eval'ed source");
|
||||
is(frame.where.source.actor, gSources.values[0], "Should have broken on the eval'ed source");
|
||||
is(frame.where.line, 2, "Should break on line 2");
|
||||
|
||||
yield resumeDebuggerThenCloseAndFinish(gPanel);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
This is the pdf.js project output, https://github.com/mozilla/pdf.js
|
||||
|
||||
Current extension version is: 1.0.1130
|
||||
Current extension version is: 1.0.1149
|
||||
|
||||
|
|
|
@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
|
|||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
PDFJS.version = '1.0.1130';
|
||||
PDFJS.build = 'e4f0ae2';
|
||||
PDFJS.version = '1.0.1149';
|
||||
PDFJS.build = 'bc7a110';
|
||||
|
||||
(function pdfjsWrapper() {
|
||||
// Use strict in our context only - users might not want it
|
||||
|
@ -356,6 +356,7 @@ function shadow(obj, prop, value) {
|
|||
writable: false });
|
||||
return value;
|
||||
}
|
||||
PDFJS.shadow = shadow;
|
||||
|
||||
var PasswordResponses = PDFJS.PasswordResponses = {
|
||||
NEED_PASSWORD: 1,
|
||||
|
|
|
@ -22,8 +22,8 @@ if (typeof PDFJS === 'undefined') {
|
|||
(typeof window !== 'undefined' ? window : this).PDFJS = {};
|
||||
}
|
||||
|
||||
PDFJS.version = '1.0.1130';
|
||||
PDFJS.build = 'e4f0ae2';
|
||||
PDFJS.version = '1.0.1149';
|
||||
PDFJS.build = 'bc7a110';
|
||||
|
||||
(function pdfjsWrapper() {
|
||||
// Use strict in our context only - users might not want it
|
||||
|
@ -356,6 +356,7 @@ function shadow(obj, prop, value) {
|
|||
writable: false });
|
||||
return value;
|
||||
}
|
||||
PDFJS.shadow = shadow;
|
||||
|
||||
var PasswordResponses = PDFJS.PasswordResponses = {
|
||||
NEED_PASSWORD: 1,
|
||||
|
@ -4800,7 +4801,7 @@ var LinkAnnotation = (function LinkAnnotationClosure() {
|
|||
data.annotationType = AnnotationType.LINK;
|
||||
|
||||
var action = dict.get('A');
|
||||
if (action) {
|
||||
if (action && isDict(action)) {
|
||||
var linkType = action.get('S').name;
|
||||
if (linkType === 'URI') {
|
||||
var url = action.get('URI');
|
||||
|
@ -15743,6 +15744,10 @@ var ToUnicodeMap = (function ToUnicodeMapClosure() {
|
|||
}
|
||||
},
|
||||
|
||||
has: function(i) {
|
||||
return this._map[i] !== undefined;
|
||||
},
|
||||
|
||||
get: function(i) {
|
||||
return this._map[i];
|
||||
},
|
||||
|
@ -15772,6 +15777,10 @@ var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
|
|||
}
|
||||
},
|
||||
|
||||
has: function (i) {
|
||||
return this.firstChar <= i && i <= this.lastChar;
|
||||
},
|
||||
|
||||
get: function (i) {
|
||||
if (this.firstChar <= i && i <= this.lastChar) {
|
||||
return String.fromCharCode(i);
|
||||
|
@ -16204,7 +16213,6 @@ var Font = (function FontClosure() {
|
|||
var isSymbolic = !!(properties.flags & FontFlags.Symbolic);
|
||||
var isIdentityUnicode =
|
||||
properties.toUnicode instanceof IdentityToUnicodeMap;
|
||||
var isCidFontType2 = (properties.type === 'CIDFontType2');
|
||||
var newMap = Object.create(null);
|
||||
var toFontChar = [];
|
||||
var usedFontCharCodes = [];
|
||||
|
@ -16215,17 +16223,11 @@ var Font = (function FontClosure() {
|
|||
var fontCharCode = originalCharCode;
|
||||
// First try to map the value to a unicode position if a non identity map
|
||||
// was created.
|
||||
if (!isIdentityUnicode) {
|
||||
if (toUnicode.get(originalCharCode) !== undefined) {
|
||||
var unicode = toUnicode.get(fontCharCode);
|
||||
// TODO: Try to map ligatures to the correct spot.
|
||||
if (unicode.length === 1) {
|
||||
fontCharCode = unicode.charCodeAt(0);
|
||||
}
|
||||
} else if (isCidFontType2) {
|
||||
// For CIDFontType2, move characters not present in toUnicode
|
||||
// to the private use area (fixes bug 1028735 and issue 4881).
|
||||
fontCharCode = nextAvailableFontCharCode;
|
||||
if (!isIdentityUnicode && toUnicode.has(originalCharCode)) {
|
||||
var unicode = toUnicode.get(fontCharCode);
|
||||
// TODO: Try to map ligatures to the correct spot.
|
||||
if (unicode.length === 1) {
|
||||
fontCharCode = unicode.charCodeAt(0);
|
||||
}
|
||||
}
|
||||
// Try to move control characters, special characters and already mapped
|
||||
|
@ -16715,13 +16717,20 @@ var Font = (function FontClosure() {
|
|||
var offset = font.getInt32() >>> 0;
|
||||
var useTable = false;
|
||||
|
||||
if (platformId === 1 && encodingId === 0) {
|
||||
if (platformId === 0 && encodingId === 0) {
|
||||
useTable = true;
|
||||
// Continue the loop since there still may be a higher priority
|
||||
// table.
|
||||
} else if (!isSymbolicFont && platformId === 3 && encodingId === 1) {
|
||||
} else if (platformId === 1 && encodingId === 0) {
|
||||
useTable = true;
|
||||
canBreak = true;
|
||||
// Continue the loop since there still may be a higher priority
|
||||
// table.
|
||||
} else if (platformId === 3 && encodingId === 1 &&
|
||||
(!isSymbolicFont || !potentialTable)) {
|
||||
useTable = true;
|
||||
if (!isSymbolicFont) {
|
||||
canBreak = true;
|
||||
}
|
||||
} else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
|
||||
useTable = true;
|
||||
canBreak = true;
|
||||
|
@ -17066,6 +17075,7 @@ var Font = (function FontClosure() {
|
|||
var newGlyfData = new Uint8Array(oldGlyfDataLength);
|
||||
var startOffset = itemDecode(locaData, 0);
|
||||
var writeOffset = 0;
|
||||
var missingGlyphData = {};
|
||||
itemEncode(locaData, 0, writeOffset);
|
||||
var i, j;
|
||||
for (i = 0, j = itemSize; i < numGlyphs; i++, j += itemSize) {
|
||||
|
@ -17083,6 +17093,10 @@ var Font = (function FontClosure() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (startOffset === endOffset) {
|
||||
missingGlyphData[i] = true;
|
||||
}
|
||||
|
||||
var newLength = sanitizeGlyph(oldGlyfData, startOffset, endOffset,
|
||||
newGlyfData, writeOffset, hintsValid);
|
||||
writeOffset += newLength;
|
||||
|
@ -17099,7 +17113,7 @@ var Font = (function FontClosure() {
|
|||
itemEncode(locaData, j, simpleGlyph.length);
|
||||
}
|
||||
glyf.data = simpleGlyph;
|
||||
return;
|
||||
return missingGlyphData;
|
||||
}
|
||||
|
||||
if (dupFirstEntry) {
|
||||
|
@ -17116,6 +17130,7 @@ var Font = (function FontClosure() {
|
|||
} else {
|
||||
glyf.data = newGlyfData.subarray(0, writeOffset);
|
||||
}
|
||||
return missingGlyphData;
|
||||
}
|
||||
|
||||
function readPostScriptTable(post, properties, maxpNumGlyphs) {
|
||||
|
@ -17575,11 +17590,13 @@ var Font = (function FontClosure() {
|
|||
|
||||
sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
|
||||
|
||||
var missingGlyphs = {};
|
||||
if (isTrueType) {
|
||||
var isGlyphLocationsLong = int16(tables.head.data[50],
|
||||
tables.head.data[51]);
|
||||
sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs,
|
||||
isGlyphLocationsLong, hintsValid, dupFirstEntry);
|
||||
missingGlyphs = sanitizeGlyphLocations(tables.loca, tables.glyf,
|
||||
numGlyphs, isGlyphLocationsLong,
|
||||
hintsValid, dupFirstEntry);
|
||||
}
|
||||
|
||||
if (!tables.hhea) {
|
||||
|
@ -17601,19 +17618,33 @@ var Font = (function FontClosure() {
|
|||
}
|
||||
}
|
||||
|
||||
var charCodeToGlyphId = [], charCode;
|
||||
var charCodeToGlyphId = [], charCode, toUnicode = properties.toUnicode;
|
||||
|
||||
function hasGlyph(glyphId, charCode) {
|
||||
if (!missingGlyphs[glyphId]) {
|
||||
return true;
|
||||
}
|
||||
if (charCode >= 0 && toUnicode.has(charCode)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (properties.type === 'CIDFontType2') {
|
||||
var cidToGidMap = properties.cidToGidMap || [];
|
||||
var cidToGidMapLength = cidToGidMap.length;
|
||||
var isCidToGidMapEmpty = cidToGidMap.length === 0;
|
||||
|
||||
properties.cMap.forEach(function(charCode, cid) {
|
||||
assert(cid <= 0xffff, 'Max size of CID is 65,535');
|
||||
var glyphId = -1;
|
||||
if (cidToGidMapLength === 0) {
|
||||
if (isCidToGidMapEmpty) {
|
||||
glyphId = charCode;
|
||||
} else if (cidToGidMap[cid] !== undefined) {
|
||||
glyphId = cidToGidMap[cid];
|
||||
}
|
||||
if (glyphId >= 0 && glyphId < numGlyphs) {
|
||||
|
||||
if (glyphId >= 0 && glyphId < numGlyphs &&
|
||||
hasGlyph(glyphId, charCode)) {
|
||||
charCodeToGlyphId[charCode] = glyphId;
|
||||
}
|
||||
});
|
||||
|
@ -17673,7 +17704,8 @@ var Font = (function FontClosure() {
|
|||
|
||||
var found = false;
|
||||
for (i = 0; i < cmapMappingsLength; ++i) {
|
||||
if (cmapMappings[i].charCode === unicodeOrCharCode) {
|
||||
if (cmapMappings[i].charCode === unicodeOrCharCode &&
|
||||
hasGlyph(cmapMappings[i].glyphId, unicodeOrCharCode)) {
|
||||
charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
|
||||
found = true;
|
||||
break;
|
||||
|
@ -17683,11 +17715,17 @@ var Font = (function FontClosure() {
|
|||
// Try to map using the post table. There are currently no known
|
||||
// pdfs that this fixes.
|
||||
var glyphId = properties.glyphNames.indexOf(glyphName);
|
||||
if (glyphId > 0) {
|
||||
if (glyphId > 0 && hasGlyph(glyphId, -1)) {
|
||||
charCodeToGlyphId[charCode] = glyphId;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (cmapPlatformId === 0 && cmapEncodingId === 0) {
|
||||
// Default Unicode semantics, use the charcodes as is.
|
||||
for (i = 0; i < cmapMappingsLength; ++i) {
|
||||
charCodeToGlyphId[cmapMappings[i].charCode] =
|
||||
cmapMappings[i].glyphId;
|
||||
}
|
||||
} else {
|
||||
// For (3, 0) cmap tables:
|
||||
// The charcode key being stored in charCodeToGlyphId is the lower
|
||||
|
@ -32715,6 +32753,10 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
|
|||
|
||||
var gotEOL = false;
|
||||
|
||||
if (this.byteAlign) {
|
||||
this.inputBits &= ~7;
|
||||
}
|
||||
|
||||
if (!this.eoblock && this.row === this.rows - 1) {
|
||||
this.eof = true;
|
||||
} else {
|
||||
|
@ -32738,10 +32780,6 @@ var CCITTFaxStream = (function CCITTFaxStreamClosure() {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.byteAlign && !gotEOL) {
|
||||
this.inputBits &= ~7;
|
||||
}
|
||||
|
||||
if (!this.eof && this.encoding > 0) {
|
||||
this.nextLine2D = !this.lookBits(1);
|
||||
this.eatBits(1);
|
||||
|
|
|
@ -5481,12 +5481,8 @@ var PDFViewerApplication = {
|
|||
get supportsPrinting() {
|
||||
var canvas = document.createElement('canvas');
|
||||
var value = 'mozPrintCallback' in canvas;
|
||||
// shadow
|
||||
Object.defineProperty(this, 'supportsPrinting', { value: value,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: false });
|
||||
return value;
|
||||
|
||||
return PDFJS.shadow(this, 'supportsPrinting', value);
|
||||
},
|
||||
|
||||
get supportsFullscreen() {
|
||||
|
@ -5501,50 +5497,34 @@ var PDFViewerApplication = {
|
|||
support = false;
|
||||
}
|
||||
|
||||
Object.defineProperty(this, 'supportsFullscreen', { value: support,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: false });
|
||||
return support;
|
||||
return PDFJS.shadow(this, 'supportsFullscreen', support);
|
||||
},
|
||||
|
||||
get supportsIntegratedFind() {
|
||||
var support = false;
|
||||
support = FirefoxCom.requestSync('supportsIntegratedFind');
|
||||
Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: false });
|
||||
return support;
|
||||
|
||||
return PDFJS.shadow(this, 'supportsIntegratedFind', support);
|
||||
},
|
||||
|
||||
get supportsDocumentFonts() {
|
||||
var support = true;
|
||||
support = FirefoxCom.requestSync('supportsDocumentFonts');
|
||||
Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: false });
|
||||
return support;
|
||||
|
||||
return PDFJS.shadow(this, 'supportsDocumentFonts', support);
|
||||
},
|
||||
|
||||
get supportsDocumentColors() {
|
||||
var support = true;
|
||||
support = FirefoxCom.requestSync('supportsDocumentColors');
|
||||
Object.defineProperty(this, 'supportsDocumentColors', { value: support,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: false });
|
||||
return support;
|
||||
|
||||
return PDFJS.shadow(this, 'supportsDocumentColors', support);
|
||||
},
|
||||
|
||||
get loadingBar() {
|
||||
var bar = new ProgressBar('#loadingBar', {});
|
||||
Object.defineProperty(this, 'loadingBar', { value: bar,
|
||||
enumerable: true,
|
||||
configurable: true,
|
||||
writable: false });
|
||||
return bar;
|
||||
|
||||
return PDFJS.shadow(this, 'loadingBar', bar);
|
||||
},
|
||||
|
||||
initPassiveLoading: function pdfViewInitPassiveLoading() {
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
## LOCALIZATION NOTE(clientShortname2): This should not be localized and
|
||||
## should remain "Firefox Hello" for all locales.
|
||||
clientShortname2=Firefox Hello
|
||||
clientSuperShortname=Hello
|
||||
|
||||
rooms_tab_button_tooltip=Conversations
|
||||
contacts_tab_button_tooltip=Contacts
|
||||
|
@ -43,10 +44,11 @@ problem_accessing_account=There Was A Problem Accessing Your Account
|
|||
## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error for location
|
||||
retry_button=Retry
|
||||
|
||||
share_email_subject4={{clientShortname}} — Join the conversation
|
||||
share_email_subject5={{clientShortname2}} — Join the conversation
|
||||
## LOCALIZATION NOTE (share_email_body4): In this item, don't translate the
|
||||
## part between {{..}} and leave the \r\n\r\n part alone
|
||||
share_email_body4=Hello!\r\n\r\nJoin me for a video conversation using {{clientShortname}}:\r\n\r\nYou don't have to download or install anything. Just copy and paste this URL into your browser:\r\n\r\n{{callUrl}}\r\n\r\nIf you want, you can also learn more about {{clientShortname}} at {{learnMoreUrl}}\r\n\r\nTalk to you soon!
|
||||
## part between {{..}} and leave the \n\n part alone
|
||||
share_email_body5=Hello!\n\nJoin me for a video conversation on {{clientShortname2}}.\n\nIt's the easiest way to connect by video with anyone anywhere. With {{clientSuperShortname}}, you don't have to download or install anything. Just click or paste this link into your {{brandShortname}}, Opera, or Chrome browser to join the conversation:\n\n{{callUrl}}\n\nIf you'd like to learn more about {{clientSuperShortname}} and how you can start your own free video conversations, visit {{learnMoreUrl}}\n\nTalk to you soon!
|
||||
|
||||
share_button2=Email Link
|
||||
copy_url_button2=Copy Link
|
||||
copied_url_button=Copied!
|
||||
|
@ -167,6 +169,14 @@ audio_call_menu_button=Audio Conversation
|
|||
## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#contacts
|
||||
video_call_menu_button=Video Conversation
|
||||
|
||||
## LOCALIZATION NOTE(gravatars_promo_message): The {{learn_more}} part will be
|
||||
## replaced with a link with the text content of gravatars_promo_message_learnmore.
|
||||
gravatars_promo_message=You can automatically add profile icons to your contacts \
|
||||
by sharing their email addresses with Gravatar. {{learn_more}}.
|
||||
gravatars_promo_message_learnmore=Learn more
|
||||
gravatars_promo_button_nothanks=No Thanks
|
||||
gravatars_promo_button_use=Use Profile Icons
|
||||
|
||||
# Conversation Window Strings
|
||||
|
||||
initiate_call_button_label2=Ready to start your conversation?
|
||||
|
@ -185,6 +195,7 @@ mute_local_video_button_title=Mute your video
|
|||
unmute_local_video_button_title=Unmute your video
|
||||
active_screenshare_button_title=Stop sharing
|
||||
inactive_screenshare_button_title=Share your screen
|
||||
share_tabs_button_title=Share my Tabs
|
||||
share_windows_button_title=Share other Windows
|
||||
|
||||
## LOCALIZATION NOTE (call_with_contact_title): The title displayed
|
||||
|
@ -312,3 +323,11 @@ rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
|
|||
rooms_room_joined_label=Someone has joined the conversation!
|
||||
rooms_room_join_label=Join the conversation
|
||||
rooms_signout_alert=Open conversations will be closed
|
||||
|
||||
# Infobar strings
|
||||
|
||||
infobar_screenshare_browser_message=Users in your conversation will now be able to see the contents of any tab you click on.
|
||||
infobar_button_gotit_label=Got it!
|
||||
infobar_button_gotit_accesskey=G
|
||||
infobar_menuitem_dontshowagain_label=Don't show this again
|
||||
infobar_menuitem_dontshowagain_accesskey=D
|
||||
|
|
|
@ -13,6 +13,10 @@ phishBeforeText=Selecting this option will send the address of web pages you are
|
|||
|
||||
labelDefaultFont=Default (%S)
|
||||
|
||||
veryLargeMinimumFontTitle=Large minimum font size
|
||||
veryLargeMinimumFontWarning=You have selected a very large minimum font size (more than 24 pixels). This may make it difficult or impossible to use some important configuration pages like this one.
|
||||
acceptVeryLargeMinimumFont=Keep my changes anyway
|
||||
|
||||
#### Permissions Manager
|
||||
|
||||
cookiepermissionstext=You can specify which websites are always or never allowed to use cookies. Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow.
|
||||
|
|
|
@ -23,5 +23,12 @@ cmd_clearHistory_accesskey=H
|
|||
cmd_showSuggestions=Show Suggestions
|
||||
cmd_showSuggestions_accesskey=S
|
||||
|
||||
# LOCALIZATION NOTE (cmd_addFoundEngine): %S is replaced by the name of
|
||||
# a search engine offered by a web page. Each engine is displayed as a
|
||||
# menuitem at the bottom of the search panel.
|
||||
cmd_addFoundEngine=Add "%S"
|
||||
|
||||
# LOCALIZATION NOTE (cmd_addFoundEngineMenu): When more than 5 engines
|
||||
# are offered by a web page, instead of listing all of them in the
|
||||
# search panel using the cmd_addFoundEngine string, they will be
|
||||
# grouped in a submenu using cmd_addFoundEngineMenu as a label.
|
||||
cmd_addFoundEngineMenu=Add search engine
|
||||
|
|
|
@ -198,7 +198,9 @@ let AboutHome = {
|
|||
#endif
|
||||
// Trigger a search through nsISearchEngine.getSubmission()
|
||||
let submission = engine.getSubmission(data.searchTerms, null, "homepage");
|
||||
window.loadURI(submission.uri.spec, null, submission.postData);
|
||||
let where = data.useNewTab ? "tab" : "current";
|
||||
window.openUILinkIn(submission.uri.spec, where, false,
|
||||
submission.postData);
|
||||
|
||||
// Used for testing
|
||||
let mm = aMessage.target.messageManager;
|
||||
|
|
|
@ -599,6 +599,10 @@ this.BrowserUITelemetry = {
|
|||
this._countEvent(["click-builtin-item", source, "search-settings"]);
|
||||
},
|
||||
|
||||
countPanicEvent: function(timeId) {
|
||||
this._countEvent(["forget-button", timeId]);
|
||||
},
|
||||
|
||||
_logAwesomeBarSearchResult: function (url) {
|
||||
let spec = Services.search.parseSubmissionURL(url);
|
||||
if (spec.engine) {
|
||||
|
|
|
@ -210,6 +210,11 @@ this.ContentSearch = {
|
|||
let engine = Services.search.getEngineByName(data.engineName);
|
||||
let submission = engine.getSubmission(data.searchString, "", data.whence);
|
||||
let browser = msg.target;
|
||||
let newTab;
|
||||
if (data.useNewTab) {
|
||||
newTab = browser.getTabBrowser().addTab();
|
||||
browser = newTab.linkedBrowser;
|
||||
}
|
||||
try {
|
||||
browser.loadURIWithFlags(submission.uri.spec,
|
||||
Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null,
|
||||
|
@ -221,6 +226,9 @@ this.ContentSearch = {
|
|||
// method on it will throw.
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (data.useNewTab) {
|
||||
browser.getTabBrowser().selectedTab = newTab;
|
||||
}
|
||||
let win = browser.ownerDocument.defaultView;
|
||||
win.BrowserSearch.recordSearchInHealthReport(engine, data.whence,
|
||||
data.selection || null);
|
||||
|
|
|
@ -116,10 +116,13 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
|
|||
|
||||
.search-panel-current-engine {
|
||||
border-top: none !important;
|
||||
border-bottom: 1px solid #ccc;
|
||||
-moz-box-align: center;
|
||||
}
|
||||
|
||||
.search-panel-current-engine:not([showonlysettings]) {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.search-panel-header {
|
||||
font-weight: normal;
|
||||
background-color: rgb(245, 245, 245);
|
||||
|
|
|
@ -139,10 +139,13 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
|
|||
|
||||
.search-panel-current-engine {
|
||||
border-top: none !important;
|
||||
border-bottom: 1px solid #ccc;
|
||||
border-radius: 4px 4px 0 0;
|
||||
}
|
||||
|
||||
.search-panel-current-engine:not([showonlysettings]) {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.search-panel-header {
|
||||
font-size: 10px;
|
||||
font-weight: normal;
|
||||
|
@ -287,10 +290,6 @@ searchbar[oneoffui] .searchbar-engine-button {
|
|||
min-height: 32px;
|
||||
}
|
||||
|
||||
.search-setting-button[showonlysettings] {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.search-setting-button[selected] {
|
||||
background-color: #d3d3d3;
|
||||
border-top-color: #bdbebe;
|
||||
|
|
|
@ -128,10 +128,13 @@ searchbar[oneoffui] .search-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon
|
|||
|
||||
.search-panel-current-engine {
|
||||
border-top: none !important;
|
||||
border-bottom: 1px solid #ccc;
|
||||
-moz-box-align: center;
|
||||
}
|
||||
|
||||
.search-panel-current-engine:not([showonlysettings]) {
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.search-panel-header {
|
||||
font-weight: normal;
|
||||
background-color: rgb(245, 245, 245);
|
||||
|
|
|
@ -8535,6 +8535,7 @@ AC_SUBST(MOZ_WEBSMS_BACKEND)
|
|||
AC_SUBST(MOZ_ANDROID_BEAM)
|
||||
AC_SUBST(MOZ_LOCALE_SWITCHER)
|
||||
AC_SUBST(MOZ_DISABLE_GECKOVIEW)
|
||||
AC_SUBST(MOZ_ANDROID_GECKOLIBS_AAR)
|
||||
AC_SUBST(MOZ_ANDROID_READING_LIST_SERVICE)
|
||||
AC_SUBST(MOZ_ANDROID_SEARCH_ACTIVITY)
|
||||
AC_SUBST(MOZ_ANDROID_SHARE_OVERLAY)
|
||||
|
|
|
@ -9,6 +9,7 @@ TEST_DIRS += ['test']
|
|||
XPIDL_SOURCES += [
|
||||
'nsIConsoleAPIStorage.idl',
|
||||
'nsIContentPolicy.idl',
|
||||
'nsIContentPolicyBase.idl',
|
||||
'nsIDocumentEncoder.idl',
|
||||
'nsIDOMDataChannel.idl',
|
||||
'nsIDOMDOMCursor.idl',
|
||||
|
@ -32,6 +33,7 @@ XPIDL_SOURCES += [
|
|||
'nsISelectionDisplay.idl',
|
||||
'nsISelectionListener.idl',
|
||||
'nsISelectionPrivate.idl',
|
||||
'nsISimpleContentPolicy.idl',
|
||||
'nsISiteSpecificUserAgent.idl',
|
||||
'nsISlowScriptDebug.idl',
|
||||
'nsIXMLHttpRequest.idl',
|
||||
|
|
|
@ -38,6 +38,7 @@ NS_NewContentPolicy(nsIContentPolicy **aResult)
|
|||
|
||||
nsContentPolicy::nsContentPolicy()
|
||||
: mPolicies(NS_CONTENTPOLICY_CATEGORY)
|
||||
, mSimplePolicies(NS_SIMPLECONTENTPOLICY_CATEGORY)
|
||||
{
|
||||
#ifdef PR_LOGGING
|
||||
if (! gConPolLog) {
|
||||
|
@ -70,6 +71,7 @@ nsContentPolicy::~nsContentPolicy()
|
|||
|
||||
inline nsresult
|
||||
nsContentPolicy::CheckPolicy(CPMethod policyMethod,
|
||||
SCPMethod simplePolicyMethod,
|
||||
uint32_t contentType,
|
||||
nsIURI *contentLocation,
|
||||
nsIURI *requestingLocation,
|
||||
|
@ -133,6 +135,53 @@ nsContentPolicy::CheckPolicy(CPMethod policyMethod,
|
|||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDOMElement> topFrameElement;
|
||||
bool isTopLevel = true;
|
||||
nsCOMPtr<nsPIDOMWindow> window;
|
||||
if (nsCOMPtr<nsINode> node = do_QueryInterface(requestingContext)) {
|
||||
window = node->OwnerDoc()->GetWindow();
|
||||
} else {
|
||||
window = do_QueryInterface(requestingContext);
|
||||
}
|
||||
|
||||
if (window) {
|
||||
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell();
|
||||
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
|
||||
loadContext->GetTopFrameElement(getter_AddRefs(topFrameElement));
|
||||
|
||||
MOZ_ASSERT(window->IsOuterWindow());
|
||||
|
||||
if (topFrameElement) {
|
||||
nsCOMPtr<nsIDOMWindow> topWindow;
|
||||
window->GetScriptableTop(getter_AddRefs(topWindow));
|
||||
isTopLevel = topWindow == static_cast<nsIDOMWindow*>(window);
|
||||
} else {
|
||||
// If we don't have a top frame element, then requestingContext is
|
||||
// part of the top-level XUL document. Presumably it's the <browser>
|
||||
// element that content is being loaded into, so we call it the
|
||||
// topFrameElement.
|
||||
topFrameElement = do_QueryInterface(requestingContext);
|
||||
isTopLevel = true;
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMArray<nsISimpleContentPolicy> simpleEntries;
|
||||
mSimplePolicies.GetEntries(simpleEntries);
|
||||
count = simpleEntries.Count();
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
/* check the appropriate policy */
|
||||
rv = (simpleEntries[i]->*simplePolicyMethod)(contentType, contentLocation,
|
||||
requestingLocation,
|
||||
topFrameElement, isTopLevel,
|
||||
mimeType, extra, requestPrincipal,
|
||||
decision);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && NS_CP_REJECTED(*decision)) {
|
||||
/* policy says no, no point continuing to check */
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// everyone returned failure, or no policies: sanitize result
|
||||
*decision = nsIContentPolicy::ACCEPT;
|
||||
return NS_OK;
|
||||
|
@ -185,7 +234,9 @@ nsContentPolicy::ShouldLoad(uint32_t contentType,
|
|||
{
|
||||
// ShouldProcess does not need a content location, but we do
|
||||
NS_PRECONDITION(contentLocation, "Must provide request location");
|
||||
nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldLoad, contentType,
|
||||
nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldLoad,
|
||||
&nsISimpleContentPolicy::ShouldLoad,
|
||||
contentType,
|
||||
contentLocation, requestingLocation,
|
||||
requestingContext, mimeType, extra,
|
||||
requestPrincipal, decision);
|
||||
|
@ -204,7 +255,9 @@ nsContentPolicy::ShouldProcess(uint32_t contentType,
|
|||
nsIPrincipal *requestPrincipal,
|
||||
int16_t *decision)
|
||||
{
|
||||
nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldProcess, contentType,
|
||||
nsresult rv = CheckPolicy(&nsIContentPolicy::ShouldProcess,
|
||||
&nsISimpleContentPolicy::ShouldProcess,
|
||||
contentType,
|
||||
contentLocation, requestingLocation,
|
||||
requestingContext, mimeType, extra,
|
||||
requestPrincipal, decision);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#define __nsContentPolicy_h__
|
||||
|
||||
#include "nsIContentPolicy.h"
|
||||
#include "nsISimpleContentPolicy.h"
|
||||
#include "nsCategoryCache.h"
|
||||
|
||||
/*
|
||||
|
@ -28,6 +29,7 @@ class nsContentPolicy : public nsIContentPolicy
|
|||
private:
|
||||
//Array of policies
|
||||
nsCategoryCache<nsIContentPolicy> mPolicies;
|
||||
nsCategoryCache<nsISimpleContentPolicy> mSimplePolicies;
|
||||
|
||||
//Helper type for CheckPolicy
|
||||
typedef
|
||||
|
@ -37,9 +39,17 @@ class nsContentPolicy : public nsIContentPolicy
|
|||
const nsACString &, nsISupports*, nsIPrincipal*,
|
||||
int16_t*));
|
||||
|
||||
typedef
|
||||
NS_STDCALL_FUNCPROTO(nsresult, SCPMethod, nsISimpleContentPolicy,
|
||||
ShouldProcess,
|
||||
(uint32_t, nsIURI*, nsIURI*, nsIDOMElement*, bool,
|
||||
const nsACString &, nsISupports*, nsIPrincipal*,
|
||||
int16_t*));
|
||||
|
||||
//Helper method that applies policyMethod across all policies in mPolicies
|
||||
// with the given parameters
|
||||
nsresult CheckPolicy(CPMethod policyMethod, uint32_t contentType,
|
||||
nsresult CheckPolicy(CPMethod policyMethod, SCPMethod simplePolicyMethod,
|
||||
uint32_t contentType,
|
||||
nsIURI *aURI, nsIURI *origURI,
|
||||
nsISupports *requestingContext,
|
||||
const nsACString &mimeGuess, nsISupports *extra,
|
||||
|
|
|
@ -29,6 +29,7 @@ class nsIPrincipal;
|
|||
|
||||
#define NS_CONTENTPOLICY_CONTRACTID "@mozilla.org/layout/content-policy;1"
|
||||
#define NS_CONTENTPOLICY_CATEGORY "content-policy"
|
||||
#define NS_SIMPLECONTENTPOLICY_CATEGORY "simple-content-policy"
|
||||
#define NS_CONTENTPOLICY_CID \
|
||||
{0x0e3afd3d, 0xeb60, 0x4c2b, \
|
||||
{ 0x96, 0x3b, 0x56, 0xd7, 0xc4, 0x39, 0xf1, 0x24 }}
|
||||
|
|
|
@ -1739,7 +1739,7 @@ public:
|
|||
const nsAString& aMessage,
|
||||
const StructuredCloneData& aData,
|
||||
JS::Handle<JSObject *> aCpows,
|
||||
nsIPrincipal* aPrincipal)
|
||||
nsIPrincipal* aPrincipal) MOZ_OVERRIDE
|
||||
{
|
||||
nsRefPtr<nsIRunnable> ev =
|
||||
new nsAsyncMessageToSameProcessChild(aCx, aMessage, aData, aCpows,
|
||||
|
@ -1748,25 +1748,25 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CheckPermission(const nsAString& aPermission)
|
||||
bool CheckPermission(const nsAString& aPermission) MOZ_OVERRIDE
|
||||
{
|
||||
// In a single-process scenario, the child always has all capabilities.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckManifestURL(const nsAString& aManifestURL)
|
||||
bool CheckManifestURL(const nsAString& aManifestURL) MOZ_OVERRIDE
|
||||
{
|
||||
// In a single-process scenario, the child always has all capabilities.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CheckAppHasPermission(const nsAString& aPermission)
|
||||
bool CheckAppHasPermission(const nsAString& aPermission) MOZ_OVERRIDE
|
||||
{
|
||||
// In a single-process scenario, the child always has all capabilities.
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool CheckAppHasStatus(unsigned short aStatus)
|
||||
virtual bool CheckAppHasStatus(unsigned short aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
// In a single-process scenario, the child always has all capabilities.
|
||||
return true;
|
||||
|
|
|
@ -5,16 +5,12 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIContentPolicyBase.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsIDOMNode;
|
||||
interface nsIPrincipal;
|
||||
|
||||
/**
|
||||
* The type of nsIContentPolicy::TYPE_*
|
||||
*/
|
||||
typedef unsigned long nsContentPolicyType;
|
||||
|
||||
/**
|
||||
* Interface for content policy mechanism. Implementations of this
|
||||
* interface can be used to control loading of various types of out-of-line
|
||||
|
@ -24,205 +20,9 @@ typedef unsigned long nsContentPolicyType;
|
|||
* by launching a dialog to prompt the user for something).
|
||||
*/
|
||||
|
||||
[scriptable,uuid(94513f07-d559-480e-8879-6249852365ea)]
|
||||
interface nsIContentPolicy : nsISupports
|
||||
[scriptable,uuid(447a2300-ab3c-11e4-bcd8-0800200c9a66)]
|
||||
interface nsIContentPolicy : nsIContentPolicyBase
|
||||
{
|
||||
/**
|
||||
* Indicates a unset or bogus policy type.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_INVALID = 0;
|
||||
|
||||
/**
|
||||
* Gecko/Firefox developers: Avoid using TYPE_OTHER. Especially for
|
||||
* requests that are coming from webpages. Or requests in general which
|
||||
* you expect that security checks will be done on.
|
||||
* Always use a more specific type if one is available. And do not hesitate
|
||||
* to add more types as appropriate.
|
||||
* But if you are fairly sure that no one would care about your more specific
|
||||
* type, then it's ok to use TYPE_OTHER.
|
||||
*
|
||||
* Extension developers: Whenever it is reasonable, use one of the existing
|
||||
* content types. If none of the existing content types are right for
|
||||
* something you are doing, file a bug in the Core/DOM component that
|
||||
* includes a patch that adds your new content type to the end of the list of
|
||||
* TYPE_* constants here. But, don't start using your new content type until
|
||||
* your patch has been accepted, because it will be uncertain what exact
|
||||
* value and name your new content type will have; in that interim period,
|
||||
* use TYPE_OTHER. In your patch, document your new content type in the style
|
||||
* of the existing ones. In the bug you file, provide a more detailed
|
||||
* description of the new type of content you want Gecko to support, so that
|
||||
* the existing implementations of nsIContentPolicy can be properly modified
|
||||
* to deal with that new type of content.
|
||||
*
|
||||
* Implementations of nsIContentPolicy should treat this the same way they
|
||||
* treat unknown types, because existing users of TYPE_OTHER may be converted
|
||||
* to use new content types.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_OTHER = 1;
|
||||
|
||||
/**
|
||||
* Indicates an executable script (such as JavaScript).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_SCRIPT = 2;
|
||||
|
||||
/**
|
||||
* Indicates an image (e.g., IMG elements).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_IMAGE = 3;
|
||||
|
||||
/**
|
||||
* Indicates a stylesheet (e.g., STYLE elements).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_STYLESHEET = 4;
|
||||
|
||||
/**
|
||||
* Indicates a generic object (plugin-handled content typically falls under
|
||||
* this category).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_OBJECT = 5;
|
||||
|
||||
/**
|
||||
* Indicates a document at the top-level (i.e., in a browser).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_DOCUMENT = 6;
|
||||
|
||||
/**
|
||||
* Indicates a document contained within another document (e.g., IFRAMEs,
|
||||
* FRAMES, and OBJECTs).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_SUBDOCUMENT = 7;
|
||||
|
||||
/**
|
||||
* Indicates a timed refresh.
|
||||
*
|
||||
* shouldLoad will never get this, because it does not represent content
|
||||
* to be loaded (the actual load triggered by the refresh will go through
|
||||
* shouldLoad as expected).
|
||||
*
|
||||
* shouldProcess will get this for, e.g., META Refresh elements and HTTP
|
||||
* Refresh headers.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_REFRESH = 8;
|
||||
|
||||
/**
|
||||
* Indicates an XBL binding request, triggered either by -moz-binding CSS
|
||||
* property.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_XBL = 9;
|
||||
|
||||
/**
|
||||
* Indicates a ping triggered by a click on <A PING="..."> element.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_PING = 10;
|
||||
|
||||
/**
|
||||
* Indicates an XMLHttpRequest. Also used for document.load and for EventSource.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_XMLHTTPREQUEST = 11;
|
||||
const nsContentPolicyType TYPE_DATAREQUEST = 11; // alias
|
||||
|
||||
/**
|
||||
* Indicates a request by a plugin.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_OBJECT_SUBREQUEST = 12;
|
||||
|
||||
/**
|
||||
* Indicates a DTD loaded by an XML document.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_DTD = 13;
|
||||
|
||||
/**
|
||||
* Indicates a font loaded via @font-face rule.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_FONT = 14;
|
||||
|
||||
/**
|
||||
* Indicates a video or audio load.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_MEDIA = 15;
|
||||
|
||||
/**
|
||||
* Indicates a WebSocket load.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_WEBSOCKET = 16;
|
||||
|
||||
/**
|
||||
* Indicates a Content Security Policy report.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_CSP_REPORT = 17;
|
||||
|
||||
/**
|
||||
* Indicates a style sheet transformation.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_XSLT = 18;
|
||||
|
||||
/**
|
||||
* Indicates a beacon post.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_BEACON = 19;
|
||||
|
||||
/**
|
||||
* Indicates a load initiated by the fetch() function from the Fetch
|
||||
* specification.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_FETCH = 20;
|
||||
|
||||
/**
|
||||
* Indicates a <img srcset> or <picture> request.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_IMAGESET = 21;
|
||||
|
||||
/* When adding new content types, please update nsContentBlocker,
|
||||
* NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
|
||||
* implementations, and other things that are not listed here that are
|
||||
* related to nsIContentPolicy. */
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load or process request
|
||||
* is rejected based on details of the request.
|
||||
*/
|
||||
const short REJECT_REQUEST = -1;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load/process is rejected
|
||||
* based solely on its type (of the above flags).
|
||||
*
|
||||
* NOTE that it is not meant to stop future requests for this type--only the
|
||||
* current request.
|
||||
*/
|
||||
const short REJECT_TYPE = -2;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load/process is rejected
|
||||
* based on the server it is hosted on or requested from (aContentLocation or
|
||||
* aRequestOrigin), e.g., if you block an IMAGE because it is served from
|
||||
* goatse.cx (even if you don't necessarily block other types from that
|
||||
* server/domain).
|
||||
*
|
||||
* NOTE that it is not meant to stop future requests for this server--only the
|
||||
* current request.
|
||||
*/
|
||||
const short REJECT_SERVER = -3;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load/process is rejected
|
||||
* based on some other criteria. Mozilla callers will handle this like
|
||||
* REJECT_REQUEST; third-party implementors may, for example, use this to
|
||||
* direct their own callers to consult the extra parameter for additional
|
||||
* details.
|
||||
*/
|
||||
const short REJECT_OTHER = -4;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load or process request
|
||||
* is not rejected.
|
||||
*/
|
||||
const short ACCEPT = 1;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Should the resource at this location be loaded?
|
||||
* ShouldLoad will be called before loading the resource at aContentLocation
|
||||
|
@ -328,5 +128,4 @@ interface nsIContentPolicy : nsISupports
|
|||
in ACString aMimeType,
|
||||
in nsISupports aExtra,
|
||||
[optional] in nsIPrincipal aRequestPrincipal);
|
||||
|
||||
};
|
||||
|
|
|
@ -0,0 +1,223 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ft=cpp tw=78 sw=2 et ts=8 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsIDOMNode;
|
||||
interface nsIPrincipal;
|
||||
|
||||
/**
|
||||
* The type of nsIContentPolicy::TYPE_*
|
||||
*/
|
||||
typedef unsigned long nsContentPolicyType;
|
||||
|
||||
/**
|
||||
* Interface for content policy mechanism. Implementations of this
|
||||
* interface can be used to control loading of various types of out-of-line
|
||||
* content, or processing of certain types of in-line content.
|
||||
*
|
||||
* WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
|
||||
* by launching a dialog to prompt the user for something).
|
||||
*/
|
||||
|
||||
[scriptable,uuid(21bb54b0-ab3c-11e4-bcd8-0800200c9a66)]
|
||||
interface nsIContentPolicyBase : nsISupports
|
||||
{
|
||||
/**
|
||||
* Indicates a unset or bogus policy type.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_INVALID = 0;
|
||||
|
||||
/**
|
||||
* Gecko/Firefox developers: Avoid using TYPE_OTHER. Especially for
|
||||
* requests that are coming from webpages. Or requests in general which
|
||||
* you expect that security checks will be done on.
|
||||
* Always use a more specific type if one is available. And do not hesitate
|
||||
* to add more types as appropriate.
|
||||
* But if you are fairly sure that no one would care about your more specific
|
||||
* type, then it's ok to use TYPE_OTHER.
|
||||
*
|
||||
* Extension developers: Whenever it is reasonable, use one of the existing
|
||||
* content types. If none of the existing content types are right for
|
||||
* something you are doing, file a bug in the Core/DOM component that
|
||||
* includes a patch that adds your new content type to the end of the list of
|
||||
* TYPE_* constants here. But, don't start using your new content type until
|
||||
* your patch has been accepted, because it will be uncertain what exact
|
||||
* value and name your new content type will have; in that interim period,
|
||||
* use TYPE_OTHER. In your patch, document your new content type in the style
|
||||
* of the existing ones. In the bug you file, provide a more detailed
|
||||
* description of the new type of content you want Gecko to support, so that
|
||||
* the existing implementations of nsIContentPolicy can be properly modified
|
||||
* to deal with that new type of content.
|
||||
*
|
||||
* Implementations of nsIContentPolicy should treat this the same way they
|
||||
* treat unknown types, because existing users of TYPE_OTHER may be converted
|
||||
* to use new content types.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_OTHER = 1;
|
||||
|
||||
/**
|
||||
* Indicates an executable script (such as JavaScript).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_SCRIPT = 2;
|
||||
|
||||
/**
|
||||
* Indicates an image (e.g., IMG elements).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_IMAGE = 3;
|
||||
|
||||
/**
|
||||
* Indicates a stylesheet (e.g., STYLE elements).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_STYLESHEET = 4;
|
||||
|
||||
/**
|
||||
* Indicates a generic object (plugin-handled content typically falls under
|
||||
* this category).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_OBJECT = 5;
|
||||
|
||||
/**
|
||||
* Indicates a document at the top-level (i.e., in a browser).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_DOCUMENT = 6;
|
||||
|
||||
/**
|
||||
* Indicates a document contained within another document (e.g., IFRAMEs,
|
||||
* FRAMES, and OBJECTs).
|
||||
*/
|
||||
const nsContentPolicyType TYPE_SUBDOCUMENT = 7;
|
||||
|
||||
/**
|
||||
* Indicates a timed refresh.
|
||||
*
|
||||
* shouldLoad will never get this, because it does not represent content
|
||||
* to be loaded (the actual load triggered by the refresh will go through
|
||||
* shouldLoad as expected).
|
||||
*
|
||||
* shouldProcess will get this for, e.g., META Refresh elements and HTTP
|
||||
* Refresh headers.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_REFRESH = 8;
|
||||
|
||||
/**
|
||||
* Indicates an XBL binding request, triggered either by -moz-binding CSS
|
||||
* property.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_XBL = 9;
|
||||
|
||||
/**
|
||||
* Indicates a ping triggered by a click on <A PING="..."> element.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_PING = 10;
|
||||
|
||||
/**
|
||||
* Indicates an XMLHttpRequest. Also used for document.load and for EventSource.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_XMLHTTPREQUEST = 11;
|
||||
const nsContentPolicyType TYPE_DATAREQUEST = 11; // alias
|
||||
|
||||
/**
|
||||
* Indicates a request by a plugin.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_OBJECT_SUBREQUEST = 12;
|
||||
|
||||
/**
|
||||
* Indicates a DTD loaded by an XML document.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_DTD = 13;
|
||||
|
||||
/**
|
||||
* Indicates a font loaded via @font-face rule.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_FONT = 14;
|
||||
|
||||
/**
|
||||
* Indicates a video or audio load.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_MEDIA = 15;
|
||||
|
||||
/**
|
||||
* Indicates a WebSocket load.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_WEBSOCKET = 16;
|
||||
|
||||
/**
|
||||
* Indicates a Content Security Policy report.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_CSP_REPORT = 17;
|
||||
|
||||
/**
|
||||
* Indicates a style sheet transformation.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_XSLT = 18;
|
||||
|
||||
/**
|
||||
* Indicates a beacon post.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_BEACON = 19;
|
||||
|
||||
/**
|
||||
* Indicates a load initiated by the fetch() function from the Fetch
|
||||
* specification.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_FETCH = 20;
|
||||
|
||||
/**
|
||||
* Indicates a <img srcset> or <picture> request.
|
||||
*/
|
||||
const nsContentPolicyType TYPE_IMAGESET = 21;
|
||||
|
||||
/* When adding new content types, please update nsContentBlocker,
|
||||
* NS_CP_ContentTypeName, nsCSPContext, all nsIContentPolicy
|
||||
* implementations, and other things that are not listed here that are
|
||||
* related to nsIContentPolicy. */
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load or process request
|
||||
* is rejected based on details of the request.
|
||||
*/
|
||||
const short REJECT_REQUEST = -1;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load/process is rejected
|
||||
* based solely on its type (of the above flags).
|
||||
*
|
||||
* NOTE that it is not meant to stop future requests for this type--only the
|
||||
* current request.
|
||||
*/
|
||||
const short REJECT_TYPE = -2;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load/process is rejected
|
||||
* based on the server it is hosted on or requested from (aContentLocation or
|
||||
* aRequestOrigin), e.g., if you block an IMAGE because it is served from
|
||||
* goatse.cx (even if you don't necessarily block other types from that
|
||||
* server/domain).
|
||||
*
|
||||
* NOTE that it is not meant to stop future requests for this server--only the
|
||||
* current request.
|
||||
*/
|
||||
const short REJECT_SERVER = -3;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load/process is rejected
|
||||
* based on some other criteria. Mozilla callers will handle this like
|
||||
* REJECT_REQUEST; third-party implementors may, for example, use this to
|
||||
* direct their own callers to consult the extra parameter for additional
|
||||
* details.
|
||||
*/
|
||||
const short REJECT_OTHER = -4;
|
||||
|
||||
/**
|
||||
* Returned from shouldLoad or shouldProcess if the load or process request
|
||||
* is not rejected.
|
||||
*/
|
||||
const short ACCEPT = 1;
|
||||
};
|
|
@ -0,0 +1,147 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ft=cpp tw=78 sw=2 et ts=8 : */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIContentPolicyBase.idl"
|
||||
|
||||
interface nsIURI;
|
||||
interface nsIDOMNode;
|
||||
interface nsIPrincipal;
|
||||
interface nsIDOMElement;
|
||||
|
||||
/**
|
||||
* Interface for content policy mechanism. Implementations of this
|
||||
* interface can be used to control loading of various types of out-of-line
|
||||
* content, or processing of certain types of in-line content.
|
||||
*
|
||||
* This interface differs from nsIContentPolicy in that it offers less control
|
||||
* (the DOM node doing the load is not provided) but more flexibility for
|
||||
* Gecko. In particular, this interface allows an add-on in the chrome process
|
||||
* to block loads without using cross-process wrappers (CPOWs). Add-ons should
|
||||
* prefer this interface to nsIContentPolicy because it should be faster in
|
||||
* e10s. In the future, it may also be run asynchronously.
|
||||
*
|
||||
* WARNING: do not block the caller from shouldLoad or shouldProcess (e.g.,
|
||||
* by launching a dialog to prompt the user for something).
|
||||
*/
|
||||
|
||||
[scriptable,uuid(83d93c70-ab46-11e4-bcd8-0800200c9a66)]
|
||||
interface nsISimpleContentPolicy : nsIContentPolicyBase
|
||||
{
|
||||
/**
|
||||
* Should the resource at this location be loaded?
|
||||
* ShouldLoad will be called before loading the resource at aContentLocation
|
||||
* to determine whether to start the load at all.
|
||||
*
|
||||
* @param aContentType the type of content being tested. This will be one
|
||||
* one of the TYPE_* constants.
|
||||
*
|
||||
* @param aContentLocation the location of the content being checked; must
|
||||
* not be null
|
||||
*
|
||||
* @param aRequestOrigin OPTIONAL. the location of the resource that
|
||||
* initiated this load request; can be null if
|
||||
* inapplicable
|
||||
*
|
||||
* @param aTopFrameElement OPTIONAL. The top frame element (typically a
|
||||
* <xul:browser> element) that initiated the
|
||||
* request. In a content process, this argument
|
||||
* will be null.
|
||||
*
|
||||
* @param aIsTopLevel OPTIONAL. True iff the request was initiated
|
||||
* from a frame where |window.top === window|.
|
||||
*
|
||||
* @param aMimeTypeGuess OPTIONAL. a guess for the requested content's
|
||||
* MIME type, based on information available to
|
||||
* the request initiator (e.g., an OBJECT's type
|
||||
* attribute); does not reliably reflect the
|
||||
* actual MIME type of the requested content
|
||||
*
|
||||
* @param aExtra an OPTIONAL argument, pass-through for non-Gecko
|
||||
* callers to pass extra data to callees.
|
||||
*
|
||||
* @param aRequestPrincipal an OPTIONAL argument, defines the principal that
|
||||
* caused the load. This is optional only for
|
||||
* non-gecko code: all gecko code should set this
|
||||
* argument. For navigation events, this is
|
||||
* the principal of the page that caused this load.
|
||||
*
|
||||
* @return ACCEPT or REJECT_*
|
||||
*
|
||||
* @note shouldLoad can be called while the DOM and layout of the document
|
||||
* involved is in an inconsistent state. This means that implementors of
|
||||
* this method MUST NOT do any of the following:
|
||||
* 1) Modify the DOM in any way (e.g. setting attributes is a no-no).
|
||||
* 2) Query any DOM properties that depend on layout (e.g. offset*
|
||||
* properties).
|
||||
* 3) Query any DOM properties that depend on style (e.g. computed style).
|
||||
* 4) Query any DOM properties that depend on the current state of the DOM
|
||||
* outside the "context" node (e.g. lengths of node lists).
|
||||
* 5) [JavaScript implementations only] Access properties of any sort on any
|
||||
* object without using XPCNativeWrapper (either explicitly or
|
||||
* implicitly). Due to various DOM0 things, this leads to item 4.
|
||||
* If you do any of these things in your shouldLoad implementation, expect
|
||||
* unpredictable behavior, possibly including crashes, content not showing
|
||||
* up, content showing up doubled, etc. If you need to do any of the things
|
||||
* above, do them off timeout or event.
|
||||
*/
|
||||
short shouldLoad(in nsContentPolicyType aContentType,
|
||||
in nsIURI aContentLocation,
|
||||
in nsIURI aRequestOrigin,
|
||||
in nsIDOMElement aTopFrameElement,
|
||||
in boolean aIsTopLevel,
|
||||
in ACString aMimeTypeGuess,
|
||||
in nsISupports aExtra,
|
||||
in nsIPrincipal aRequestPrincipal);
|
||||
|
||||
/**
|
||||
* Should the resource be processed?
|
||||
* ShouldProcess will be called once all the information passed to it has
|
||||
* been determined about the resource, typically after part of the resource
|
||||
* has been loaded.
|
||||
*
|
||||
* @param aContentType the type of content being tested. This will be one
|
||||
* one of the TYPE_* constants.
|
||||
*
|
||||
* @param aContentLocation OPTIONAL; the location of the resource being
|
||||
* requested: MAY be, e.g., a post-redirection URI
|
||||
* for the resource.
|
||||
*
|
||||
* @param aRequestOrigin OPTIONAL. the location of the resource that
|
||||
* initiated this load request; can be null if
|
||||
* inapplicable
|
||||
*
|
||||
* @param aTopFrameElement OPTIONAL. The top frame element (typically a
|
||||
* <xul:browser> element) that initiated the
|
||||
* request. In a content process, this argument
|
||||
* will be null.
|
||||
*
|
||||
* @param aIsTopLevel OPTIONAL. True iff the request was initiated
|
||||
* from a frame where |window.top === window|.
|
||||
*
|
||||
* @param aMimeType the MIME type of the requested resource (e.g.,
|
||||
* image/png), as reported by the networking library,
|
||||
* if available (may be empty if inappropriate for
|
||||
* the type, e.g., TYPE_REFRESH).
|
||||
*
|
||||
* @param aExtra an OPTIONAL argument, pass-through for non-Gecko
|
||||
* callers to pass extra data to callees.
|
||||
*
|
||||
* @return ACCEPT or REJECT_*
|
||||
*
|
||||
* @note shouldProcess can be called while the DOM and layout of the document
|
||||
* involved is in an inconsistent state. See the note on shouldLoad to see
|
||||
* what this means for implementors of this method.
|
||||
*/
|
||||
short shouldProcess(in nsContentPolicyType aContentType,
|
||||
in nsIURI aContentLocation,
|
||||
in nsIURI aRequestOrigin,
|
||||
in nsIDOMElement aTopFrameElement,
|
||||
in boolean aIsTopLevel,
|
||||
in ACString aMimeType,
|
||||
in nsISupports aExtra,
|
||||
in nsIPrincipal aRequestPrincipal);
|
||||
};
|
|
@ -0,0 +1,73 @@
|
|||
const Ci = Components.interfaces;
|
||||
const Cc = Components.classes;
|
||||
const Cr = Components.results;
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function executeSoon(f)
|
||||
{
|
||||
Services.tm.mainThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
var urlSuffix = "/this/is/the/test/url";
|
||||
|
||||
// Content policy / factory implementation for the test
|
||||
var policyID = Components.ID("{6aadacff-f1f2-46f4-a6db-6d429f884a30}");
|
||||
var policyName = "@mozilla.org/simpletestpolicy;1";
|
||||
var policy = {
|
||||
// nsISupports implementation
|
||||
QueryInterface:
|
||||
XPCOMUtils.generateQI([
|
||||
Ci.nsISupports,
|
||||
Ci.nsIFactory,
|
||||
Ci.nsISimpleContentPolicy]),
|
||||
|
||||
// nsIFactory implementation
|
||||
createInstance: function(outer, iid) {
|
||||
return this.QueryInterface(iid);
|
||||
},
|
||||
|
||||
// nsIContentPolicy implementation
|
||||
shouldLoad: function(contentType,
|
||||
contentLocation,
|
||||
requestOrigin,
|
||||
frame,
|
||||
isTopLevel,
|
||||
mimeTypeGuess,
|
||||
extra)
|
||||
{
|
||||
// Remember last content type seen for the test url
|
||||
if (contentLocation.spec.endsWith(urlSuffix)) {
|
||||
assert.ok(frame === browserElement, "correct <browser> element");
|
||||
sendAsyncMessage("shouldLoad", {contentType: contentType, isTopLevel: isTopLevel});
|
||||
return Ci.nsIContentPolicy.REJECT_REQUEST;
|
||||
}
|
||||
|
||||
return Ci.nsIContentPolicy.ACCEPT;
|
||||
},
|
||||
|
||||
shouldProcess: function() {
|
||||
return Ci.nsIContentPolicy.ACCEPT;
|
||||
}
|
||||
}
|
||||
|
||||
// Register content policy
|
||||
var componentManager = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
|
||||
componentManager.registerFactory(policyID, "Test simple content policy", policyName, policy);
|
||||
|
||||
var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
|
||||
categoryManager.addCategoryEntry("simple-content-policy", policyName, policyName, false, true);
|
||||
|
||||
addMessageListener("finished", () => {
|
||||
// Unregister content policy
|
||||
categoryManager.deleteCategoryEntry("simple-content-policy", policyName, false);
|
||||
|
||||
executeSoon(function() {
|
||||
// Component must be unregistered delayed, otherwise other content
|
||||
// policy will not be removed from the category correctly
|
||||
componentManager.unregisterFactory(policyID, policy);
|
||||
});
|
||||
});
|
||||
|
||||
sendAsyncMessage("ready");
|
|
@ -192,6 +192,7 @@ support-files =
|
|||
file_mozfiledataurl_inner.html
|
||||
file_mozfiledataurl_text.txt
|
||||
file_restrictedEventSource.sjs
|
||||
file_simplecontentpolicy.js
|
||||
file_websocket_basic_wsh.py
|
||||
file_websocket_hello_wsh.py
|
||||
file_websocket_http_resource.txt
|
||||
|
@ -305,6 +306,8 @@ skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # Fails on b2g-desktop, track
|
|||
[test_settimeout_inner.html]
|
||||
[test_setting_opener.html]
|
||||
skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
|
||||
[test_simplecontentpolicy.html]
|
||||
skip-if = e10s || buildapp == 'b2g'
|
||||
[test_url.html]
|
||||
[test_url_data.html]
|
||||
[test_url_empty_port.html]
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for nsISimpleContentPolicy</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1128798">Mozilla Bug 1128798</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var lastContentType = -1;
|
||||
const testURL = window.location.href + "/this/is/the/test/url";
|
||||
const Cc = SpecialPowers.Cc;
|
||||
const Ci = SpecialPowers.Ci;
|
||||
|
||||
var beaconUrl = "http://mochi.test:8888/tests/dom/tests/mochitest/beacon/beacon-handler.sjs";
|
||||
|
||||
var chromeURL = SimpleTest.getTestFileURL("file_simplecontentpolicy.js");
|
||||
var script = SpecialPowers.loadChromeScript(chromeURL);
|
||||
|
||||
// Try creating different request types. Most of these are not
|
||||
// top-level because they run in the test iframe.
|
||||
var tests = [["SCRIPT", false],
|
||||
["IMAGE", false],
|
||||
["STYLESHEET", false],
|
||||
["OBJECT", false],
|
||||
["DOCUMENT", true],
|
||||
["SUBDOCUMENT", false],
|
||||
["XBL", false],
|
||||
["XMLHTTPREQUEST", false],
|
||||
["BEACON", false]];
|
||||
var curTest = -1;
|
||||
|
||||
var div;
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
script.addMessageListener("ready", function(msg) {
|
||||
SpecialPowers.pushPrefEnv({'set': [["beacon.enabled", true]]}, runNextTest);
|
||||
});
|
||||
|
||||
script.addMessageListener("shouldLoad", function(msg) {
|
||||
var isTopLevel = tests[curTest][1];
|
||||
var type = "TYPE_" + tests[curTest][0];
|
||||
is(msg.contentType, Ci.nsIContentPolicy[type], "Content policies triggered for " + type);
|
||||
is(msg.isTopLevel, isTopLevel, "isTopLevel is set correctly");
|
||||
|
||||
if (tests[curTest] == "XBL") {
|
||||
//XXX Removing binding to work-around a memory leak (bugs 478528, 499735).
|
||||
div.style.MozBinding = "";
|
||||
}
|
||||
|
||||
runNextTest();
|
||||
});
|
||||
|
||||
function runNextTest() {
|
||||
curTest++;
|
||||
if (curTest < tests.length) {
|
||||
var method = "request_" + tests[curTest][0].toLowerCase();
|
||||
try {
|
||||
window[method]();
|
||||
} catch(e) {}
|
||||
}
|
||||
else {
|
||||
script.sendAsyncMessage("finished");
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
|
||||
// Request creating functions
|
||||
|
||||
function request_script() {
|
||||
var content = $("content");
|
||||
|
||||
Math.sin(1);
|
||||
var script = document.createElement("script");
|
||||
script.setAttribute("type", "text/javascript")
|
||||
script.setAttribute("src", testURL)
|
||||
content.appendChild(script);
|
||||
}
|
||||
|
||||
function request_image() {
|
||||
var content = $("content");
|
||||
|
||||
var image = new Image();
|
||||
image.src = testURL;
|
||||
}
|
||||
|
||||
function request_stylesheet() {
|
||||
var content = $("content");
|
||||
|
||||
var stylesheet = document.createElement("link");
|
||||
stylesheet.setAttribute("rel", "stylesheet");
|
||||
stylesheet.setAttribute("type", "text/css");
|
||||
stylesheet.setAttribute("href", testURL);
|
||||
content.appendChild(stylesheet);
|
||||
}
|
||||
|
||||
function request_object() {
|
||||
var content = $("content");
|
||||
|
||||
var object = document.createElement("embed");
|
||||
object.setAttribute("src", testURL);
|
||||
content.appendChild(object);
|
||||
}
|
||||
|
||||
function request_document() {
|
||||
top.location.href = testURL;
|
||||
}
|
||||
|
||||
function request_subdocument() {
|
||||
var content = $("content");
|
||||
|
||||
var frame = document.createElement("iframe");
|
||||
frame.setAttribute("src", testURL);
|
||||
content.appendChild(frame);
|
||||
}
|
||||
|
||||
function request_xbl() {
|
||||
var content = $("content");
|
||||
|
||||
div = document.createElement("div");
|
||||
div.style.MozBinding = "url(" + testURL + ")";
|
||||
$('test').appendChild(div);
|
||||
div.offsetLeft; // Flush styles.
|
||||
}
|
||||
|
||||
function request_xmlhttprequest() {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", testURL, false);
|
||||
request.send(null);
|
||||
}
|
||||
|
||||
function request_beacon() {
|
||||
navigator.sendBeacon(testURL, "bacon would have been a better name than beacon");
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -240,6 +240,16 @@ DOMInterfaces = {
|
|||
'nativeType': 'mozilla::dom::workers::ChromeWorkerPrivate',
|
||||
},
|
||||
|
||||
'Client': {
|
||||
'nativeType': 'mozilla::dom::workers::ServiceWorkerClient',
|
||||
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClient.h',
|
||||
},
|
||||
|
||||
'Clients': {
|
||||
'nativeType': 'mozilla::dom::workers::ServiceWorkerClients',
|
||||
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h',
|
||||
},
|
||||
|
||||
'Console': {
|
||||
'implicitJSContext': [ 'trace', 'time', 'timeEnd' ],
|
||||
},
|
||||
|
@ -925,16 +935,6 @@ DOMInterfaces = {
|
|||
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorker.h',
|
||||
},
|
||||
|
||||
'ServiceWorkerClient': {
|
||||
'nativeType': 'mozilla::dom::workers::ServiceWorkerClient',
|
||||
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClient.h',
|
||||
},
|
||||
|
||||
'ServiceWorkerClients': {
|
||||
'nativeType': 'mozilla::dom::workers::ServiceWorkerClients',
|
||||
'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerClients.h',
|
||||
},
|
||||
|
||||
'ServiceWorkerGlobalScope': {
|
||||
'headerFile': 'mozilla/dom/WorkerScope.h',
|
||||
'workers': True,
|
||||
|
|
|
@ -58,3 +58,4 @@ skip-if = debug == false
|
|||
[test_promise_rejections_from_jsimplemented.html]
|
||||
skip-if = debug == false
|
||||
[test_worker_UnwrapArg.html]
|
||||
[test_crossOriginWindowSymbolAccess.html]
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html>
|
||||
<meta charset=utf-8>
|
||||
<title>Test for accessing symbols on a cross-origin window</title>
|
||||
<script src="/resources/testharness.js"></script>
|
||||
<script src="/resources/testharnessreport.js"></script>
|
||||
<div id="log"></div>
|
||||
<iframe src="http://www1.w3c-test.org/common/blank.html"></iframe>
|
||||
<script>
|
||||
async_test(function (t) {
|
||||
window.addEventListener("load", t.step_func(
|
||||
function() {
|
||||
assert_equals(document.querySelector("iframe").contentDocument, null, "Should have a crossorigin frame");
|
||||
assert_throws(new Error(), function() {
|
||||
frames[0][Symbol.iterator];
|
||||
}, "Should throw exception on cross-origin Window symbol-named get");
|
||||
assert_throws(new Error(), function() {
|
||||
frames[0].location[Symbol.iterator];
|
||||
}, "Should throw exception on cross-origin Location symbol-named get");
|
||||
t.done();
|
||||
}
|
||||
));
|
||||
}, "Check Symbol access on load");
|
||||
</script>
|
|
@ -1,17 +0,0 @@
|
|||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
PROPS2ARRAYS = $(topsrcdir)/intl/locale/props2arrays.py
|
||||
labelsencodings.properties.h: $(PROPS2ARRAYS) labelsencodings.properties
|
||||
$(PYTHON) $^ $@
|
||||
localesfallbacks.properties.h: $(PROPS2ARRAYS) localesfallbacks.properties
|
||||
$(PYTHON) $^ $@
|
||||
domainsfallbacks.properties.h: $(PROPS2ARRAYS) domainsfallbacks.properties
|
||||
$(PYTHON) $^ $@
|
||||
encodingsgroups.properties.h: $(PROPS2ARRAYS) encodingsgroups.properties
|
||||
$(PYTHON) $^ $@
|
||||
nonparticipatingdomains.properties.h: $(PROPS2ARRAYS) nonparticipatingdomains.properties
|
||||
$(PYTHON) $^ $@
|
|
@ -25,13 +25,22 @@ LOCAL_INCLUDES += [
|
|||
'/intl/locale',
|
||||
]
|
||||
|
||||
GENERATED_FILES += [
|
||||
'domainsfallbacks.properties.h',
|
||||
'encodingsgroups.properties.h',
|
||||
'labelsencodings.properties.h',
|
||||
'localesfallbacks.properties.h',
|
||||
'nonparticipatingdomains.properties.h',
|
||||
]
|
||||
props2arrays = TOPSRCDIR + '/intl/locale/props2arrays.py'
|
||||
prefixes = (
|
||||
'domainsfallbacks',
|
||||
'encodingsgroups',
|
||||
'labelsencodings',
|
||||
'localesfallbacks',
|
||||
'nonparticipatingdomains',
|
||||
)
|
||||
|
||||
for prefix in prefixes:
|
||||
input_file = prefix + '.properties'
|
||||
header = prefix + '.properties.h'
|
||||
GENERATED_FILES += [header]
|
||||
props = GENERATED_FILES[header]
|
||||
props.script = props2arrays
|
||||
props.inputs = [input_file]
|
||||
|
||||
MOCHITEST_MANIFESTS += [
|
||||
'test/mochitest.ini',
|
||||
|
|
|
@ -247,35 +247,29 @@ nsGeolocationSettings::HandleGeolocationPerOriginSettingsChange(const JS::Value&
|
|||
JS::AutoIdArray ids(cx, JS_Enumerate(cx, obj));
|
||||
|
||||
// if we get no ids then the exception list is empty and we can return here.
|
||||
if (!ids)
|
||||
if (!ids) {
|
||||
return;
|
||||
}
|
||||
|
||||
// go through all of the objects in the exceptions dictionary
|
||||
for (size_t i = 0; i < ids.length(); i++) {
|
||||
JS::RootedId id(cx);
|
||||
id = ids[i];
|
||||
|
||||
JS::RootedValue v(cx);
|
||||
if (!JS_IdToValue(cx, id, &v) || !v.isString())
|
||||
continue;
|
||||
|
||||
JS::RootedString str(cx, v.toString());
|
||||
if (!str)
|
||||
continue;
|
||||
|
||||
// get the origin for the app
|
||||
nsString origin;
|
||||
if (!AssignJSString(cx, origin, str))
|
||||
continue;
|
||||
|
||||
// if it is an app that is always precise, skip it
|
||||
if (mAlwaysPreciseApps.Contains(origin))
|
||||
nsAutoJSString origin;
|
||||
if (!origin.init(cx, id)) {
|
||||
continue;
|
||||
}
|
||||
if (mAlwaysPreciseApps.Contains(origin)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get the app setting object
|
||||
JS::RootedValue propertyValue(cx);
|
||||
if (!JS_GetPropertyById(cx, obj, id, &propertyValue) || !propertyValue.isObject())
|
||||
if (!JS_GetPropertyById(cx, obj, id, &propertyValue) || !propertyValue.isObject()) {
|
||||
continue;
|
||||
}
|
||||
JS::RootedObject settingObj(cx, &propertyValue.toObject());
|
||||
|
||||
GeolocationSetting *settings = new GeolocationSetting(origin);
|
||||
|
@ -316,10 +310,17 @@ nsGeolocationSettings::HandleGeolocationAlwaysPreciseChange(const JS::Value& aVa
|
|||
// clear the list of apps that are always precise
|
||||
mAlwaysPreciseApps.Clear();
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
JSContext* cx = jsapi.cx();
|
||||
JS::Rooted<JSObject*> obj(cx, &aVal.toObject());
|
||||
// root the object and get the global
|
||||
JS::Rooted<JSObject*> obj(nsContentUtils::RootingCx(), &aVal.toObject());
|
||||
MOZ_ASSERT(obj);
|
||||
nsIGlobalObject* global = xpc::NativeGlobal(obj);
|
||||
NS_ENSURE_TRUE_VOID(global && global->GetGlobalJSObject());
|
||||
|
||||
// the spec requires calling getters when accessing array by index
|
||||
AutoEntryScript aes(global);
|
||||
aes.TakeOwnershipOfErrorReporting();
|
||||
JSContext *cx = aes.cx();
|
||||
|
||||
if (!JS_IsArrayObject(cx, obj)) {
|
||||
return;
|
||||
}
|
||||
|
@ -337,8 +338,8 @@ nsGeolocationSettings::HandleGeolocationAlwaysPreciseChange(const JS::Value& aVa
|
|||
continue;
|
||||
}
|
||||
|
||||
nsString origin;
|
||||
if (!AssignJSString(cx, origin, value.toString())) {
|
||||
nsAutoJSString origin;
|
||||
if (!origin.init(cx, value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -351,15 +352,11 @@ nsGeolocationSettings::HandleGeolocationAlwaysPreciseChange(const JS::Value& aVa
|
|||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
GeolocationSetting::HandleTypeChange(const JS::Value& aVal)
|
||||
{
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
JSContext* cx = jsapi.cx();
|
||||
nsString str;
|
||||
if (!aVal.isString() || !AssignJSString(cx, str, aVal.toString())) {
|
||||
nsAutoJSString str;
|
||||
if (!str.init(aVal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -433,28 +430,29 @@ GeolocationSetting::HandleApproxDistanceChange(const JS::Value& aVal)
|
|||
void
|
||||
GeolocationSetting::HandleFixedCoordsChange(const JS::Value& aVal)
|
||||
{
|
||||
AutoJSAPI jsapi;
|
||||
jsapi.Init();
|
||||
JSContext* cx = jsapi.cx();
|
||||
nsString str;
|
||||
if (!aVal.isString() || !AssignJSString(cx, str, aVal.toString()) || str.IsEmpty()) {
|
||||
return;
|
||||
nsAutoJSString str;
|
||||
if (!str.init(aVal)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// parse the string and store the global lat/lon
|
||||
// the @ character is present in the GPS coord strings we receive
|
||||
// the string is in the form of @1.2345,6.7890
|
||||
// check for leading '@' and a comma in the middle
|
||||
int32_t const comma = str.Find(",");
|
||||
if ( (str.CharAt(0) != '@') || (comma == -1) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// pull the lat and lon out of the string and convert to doubles
|
||||
nsresult rv;
|
||||
nsString slat(Substring(str, 1, comma));
|
||||
nsString slat(Substring(str, 1, comma - 1));
|
||||
nsString slon(Substring(str, comma + 1));
|
||||
double lat = slat.ToDouble(&rv);
|
||||
NS_ENSURE_SUCCESS(rv,);
|
||||
double lon = slon.ToDouble(&rv);
|
||||
NS_ENSURE_SUCCESS(rv,);
|
||||
|
||||
// store the values
|
||||
mLatitude = lat;
|
||||
mLongitude = lon;
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include "nsNPAPIPluginInstance.h"
|
||||
#include "nsIWidget.h"
|
||||
#include "nsContentUtils.h"
|
||||
#ifdef XP_MACOSX
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -104,6 +108,55 @@ NS_IMPL_ELEMENT_CLONE(HTMLObjectElement)
|
|||
// nsIConstraintValidation
|
||||
NS_IMPL_NSICONSTRAINTVALIDATION(HTMLObjectElement)
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
|
||||
static nsIWidget* GetWidget(Element* aElement)
|
||||
{
|
||||
nsIWidget* retval = NULL;
|
||||
nsIFrame* frame = aElement->GetPrimaryFrame();
|
||||
if (frame) {
|
||||
retval = frame->GetNearestWidget();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void OnFocusBlurPlugin(Element* aElement, bool aFocus)
|
||||
{
|
||||
nsIWidget* widget = GetWidget(aElement);
|
||||
if (widget) {
|
||||
bool value = aFocus;
|
||||
widget->SetPluginFocused(value);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
HTMLObjectElement::HandleFocusBlurPlugin(Element* aElement,
|
||||
WidgetEvent* aEvent)
|
||||
{
|
||||
if (!aEvent->mFlags.mIsTrusted) {
|
||||
return;
|
||||
}
|
||||
switch (aEvent->message) {
|
||||
case NS_FOCUS_CONTENT: {
|
||||
OnFocusBlurPlugin(aElement, true);
|
||||
break;
|
||||
}
|
||||
case NS_BLUR_CONTENT: {
|
||||
OnFocusBlurPlugin(aElement, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLObjectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
{
|
||||
HandleFocusBlurPlugin(this, aVisitor.mEvent);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#endif // #ifdef XP_MACOSX
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLObjectElement::GetForm(nsIDOMHTMLFormElement **aForm)
|
||||
{
|
||||
|
|
|
@ -30,6 +30,12 @@ public:
|
|||
|
||||
virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// nsIDOMEventTarget
|
||||
NS_IMETHOD PostHandleEvent(EventChainPostVisitor& aVisitor) MOZ_OVERRIDE;
|
||||
static void HandleFocusBlurPlugin(Element* aElement, WidgetEvent* aEvent);
|
||||
#endif
|
||||
|
||||
// Element
|
||||
virtual bool IsInteractiveHTMLContent() const MOZ_OVERRIDE;
|
||||
|
||||
|
|
|
@ -17,6 +17,11 @@
|
|||
#include "nsIScriptError.h"
|
||||
#include "nsIWidget.h"
|
||||
#include "nsContentUtils.h"
|
||||
#ifdef XP_MACOSX
|
||||
#include "mozilla/EventDispatcher.h"
|
||||
#include "mozilla/dom/Event.h"
|
||||
#include "mozilla/dom/HTMLObjectElement.h"
|
||||
#endif
|
||||
|
||||
NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(SharedObject)
|
||||
|
||||
|
@ -108,6 +113,17 @@ NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElement)
|
|||
|
||||
NS_IMPL_ELEMENT_CLONE(HTMLSharedObjectElement)
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLSharedObjectElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
{
|
||||
HTMLObjectElement::HandleFocusBlurPlugin(this, aVisitor.mEvent);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
#endif // #ifdef XP_MACOSX
|
||||
|
||||
nsresult
|
||||
HTMLSharedObjectElement::BindToTree(nsIDocument *aDocument,
|
||||
nsIContent *aParent,
|
||||
|
|
|
@ -32,6 +32,11 @@ public:
|
|||
|
||||
virtual int32_t TabIndexDefault() MOZ_OVERRIDE;
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// nsIDOMEventTarget
|
||||
NS_IMETHOD PostHandleEvent(EventChainPostVisitor& aVisitor) MOZ_OVERRIDE;
|
||||
#endif
|
||||
|
||||
// nsIDOMHTMLAppletElement
|
||||
NS_DECL_NSIDOMHTMLAPPLETELEMENT
|
||||
|
||||
|
|
|
@ -280,6 +280,28 @@ parent:
|
|||
prio(urgent) sync EndIMEComposition(bool cancel)
|
||||
returns (bool noCompositionEvent, nsString composition);
|
||||
|
||||
/**
|
||||
* Tells chrome to start plugin IME. If this results in a string getting
|
||||
* committed, the result is in aCommitted (otherwise aCommitted is empty).
|
||||
*
|
||||
* aKeyboardEvent The event with which plugin IME is to be started
|
||||
* panelX and panelY Location in screen coordinates of the IME input panel
|
||||
* (should be just under the plugin)
|
||||
* aCommitted The string committed during IME -- otherwise empty
|
||||
*/
|
||||
prio(urgent) sync StartPluginIME(WidgetKeyboardEvent aKeyboardEvent,
|
||||
int32_t panelX, int32_t panelY)
|
||||
returns (nsString aCommitted);
|
||||
|
||||
/**
|
||||
* Tells chrome (and specifically the appropriate widget) whether or not
|
||||
* a plugin (inside the widget) has the keyboard focus. Should be sent
|
||||
* when the keyboard focus changes too or from a plugin.
|
||||
*
|
||||
* aFocused Whether or not a plugin is focused
|
||||
*/
|
||||
prio(urgent) async SetPluginFocused(bool aFocused);
|
||||
|
||||
/**
|
||||
* Request that the parent process move focus to the browser's frame. If
|
||||
* canRaise is true, the window can be raised if it is inactive.
|
||||
|
|
|
@ -2103,6 +2103,33 @@ TabParent::RecvEndIMEComposition(const bool& aCancel,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvStartPluginIME(const WidgetKeyboardEvent& aKeyboardEvent,
|
||||
const int32_t& aPanelX, const int32_t& aPanelY,
|
||||
nsString* aCommitted)
|
||||
{
|
||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||
if (!widget) {
|
||||
return true;
|
||||
}
|
||||
widget->StartPluginIME(aKeyboardEvent,
|
||||
(int32_t&)aPanelX,
|
||||
(int32_t&)aPanelY,
|
||||
*aCommitted);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvSetPluginFocused(const bool& aFocused)
|
||||
{
|
||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||
if (!widget) {
|
||||
return true;
|
||||
}
|
||||
widget->SetPluginFocused((bool&)aFocused);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
TabParent::RecvGetInputContext(int32_t* aIMEEnabled,
|
||||
int32_t* aIMEOpen,
|
||||
|
|
|
@ -192,6 +192,11 @@ public:
|
|||
virtual bool RecvEndIMEComposition(const bool& aCancel,
|
||||
bool* aNoCompositionEvent,
|
||||
nsString* aComposition) MOZ_OVERRIDE;
|
||||
virtual bool RecvStartPluginIME(const WidgetKeyboardEvent& aKeyboardEvent,
|
||||
const int32_t& aPanelX,
|
||||
const int32_t& aPanelY,
|
||||
nsString* aCommitted) MOZ_OVERRIDE;
|
||||
virtual bool RecvSetPluginFocused(const bool& aFocused) MOZ_OVERRIDE;
|
||||
virtual bool RecvGetInputContext(int32_t* aIMEEnabled,
|
||||
int32_t* aIMEOpen,
|
||||
intptr_t* aNativeIMEContext) MOZ_OVERRIDE;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
# LOCALIZATION NOTE (plugins.properties):
|
||||
# Those strings are inserted into an HTML page, so you all HTML characters
|
||||
# Those strings are inserted into an HTML page, so all HTML characters
|
||||
# have to be escaped in a way that they show up correctly in HTML!
|
||||
|
||||
title_label=About Plugins
|
||||
|
@ -21,6 +21,8 @@ description_label=Description
|
|||
suffixes_label=Suffixes
|
||||
|
||||
# GMP Plugins
|
||||
gmp_license_info=License information
|
||||
|
||||
openH264_name=OpenH264 Video Codec provided by Cisco Systems, Inc.
|
||||
openH264_description=Play back web video and use video chats.
|
||||
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include "DOMMediaStream.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsIUUIDGenerator.h"
|
||||
#include "mozilla/dom/MediaStreamBinding.h"
|
||||
#include "mozilla/dom/LocalMediaStreamBinding.h"
|
||||
#include "mozilla/dom/AudioNode.h"
|
||||
|
@ -146,6 +148,20 @@ DOMMediaStream::DOMMediaStream()
|
|||
mStream(nullptr), mHintContents(0), mTrackTypesAvailable(0),
|
||||
mNotifiedOfMediaStreamGraphShutdown(false), mCORSMode(CORS_NONE)
|
||||
{
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIUUIDGenerator> uuidgen =
|
||||
do_GetService("@mozilla.org/uuid-generator;1", &rv);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && uuidgen) {
|
||||
nsID uuid;
|
||||
memset(&uuid, 0, sizeof(uuid));
|
||||
rv = uuidgen->GenerateUUIDInPlace(&uuid);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
char buffer[NSID_LENGTH];
|
||||
uuid.ToProvidedString(buffer);
|
||||
mID = NS_ConvertASCIItoUTF16(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DOMMediaStream::~DOMMediaStream()
|
||||
|
@ -182,6 +198,12 @@ DOMMediaStream::CurrentTime()
|
|||
StreamTimeToSeconds(mStream->GetCurrentTime() - mLogicalStreamStartTime);
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::GetId(nsAString& aID) const
|
||||
{
|
||||
aID = mID;
|
||||
}
|
||||
|
||||
void
|
||||
DOMMediaStream::GetAudioTracks(nsTArray<nsRefPtr<AudioStreamTrack> >& aTracks)
|
||||
{
|
||||
|
|
|
@ -95,6 +95,8 @@ public:
|
|||
// WebIDL
|
||||
double CurrentTime();
|
||||
|
||||
void GetId(nsAString& aID) const;
|
||||
|
||||
void GetAudioTracks(nsTArray<nsRefPtr<AudioStreamTrack> >& aTracks);
|
||||
void GetVideoTracks(nsTArray<nsRefPtr<VideoStreamTrack> >& aTracks);
|
||||
void GetTracks(nsTArray<nsRefPtr<MediaStreamTrack> >& aTracks);
|
||||
|
@ -178,6 +180,10 @@ public:
|
|||
*/
|
||||
void NotifyStreamStateChanged();
|
||||
|
||||
// Webrtc allows the remote side to name a stream whatever it wants, and we
|
||||
// need to surface this to content.
|
||||
void AssignId(const nsAString& aID) { mID = aID; }
|
||||
|
||||
// Indicate what track types we eventually expect to add to this stream
|
||||
enum {
|
||||
HINT_CONTENTS_AUDIO = 1 << 0,
|
||||
|
@ -293,6 +299,8 @@ protected:
|
|||
|
||||
nsTArray<nsAutoPtr<OnTracksAvailableCallback> > mRunOnTracksAvailable;
|
||||
|
||||
nsString mID;
|
||||
|
||||
// Keep these alive until the stream finishes
|
||||
nsTArray<nsCOMPtr<nsISupports> > mConsumersToKeepAlive;
|
||||
|
||||
|
|
|
@ -99,9 +99,9 @@ GetMediaManagerLog()
|
|||
#define LOG(msg)
|
||||
#endif
|
||||
|
||||
using dom::MediaStreamConstraints; // Outside API (contains JSObject)
|
||||
using dom::MediaTrackConstraintSet; // Mandatory or optional constraints
|
||||
using dom::MediaTrackConstraints; // Raw mMandatory (as JSObject)
|
||||
using dom::MediaStreamConstraints;
|
||||
using dom::MediaTrackConstraintSet;
|
||||
using dom::MediaTrackConstraints;
|
||||
using dom::MediaStreamError;
|
||||
using dom::GetUserMediaRequest;
|
||||
using dom::Sequence;
|
||||
|
@ -431,46 +431,55 @@ VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
|
|||
|
||||
// Reminder: add handling for new constraints both here and in GetSources below!
|
||||
|
||||
bool
|
||||
VideoDevice::SatisfiesConstraintSets(
|
||||
uint32_t
|
||||
VideoDevice::GetBestFitnessDistance(
|
||||
const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
|
||||
{
|
||||
// Interrogate device-inherent properties first.
|
||||
for (size_t i = 0; i < aConstraintSets.Length(); i++) {
|
||||
auto& c = *aConstraintSets[i];
|
||||
if (c.mFacingMode.WasPassed()) {
|
||||
auto& value = c.mFacingMode.Value();
|
||||
nsString s;
|
||||
GetFacingMode(s);
|
||||
if (value.IsString()) {
|
||||
if (s != value.GetAsString()) {
|
||||
return false;
|
||||
if (!c.mFacingMode.IsConstrainDOMStringParameters() ||
|
||||
c.mFacingMode.GetAsConstrainDOMStringParameters().mExact.WasPassed()) {
|
||||
nsString deviceFacingMode;
|
||||
GetFacingMode(deviceFacingMode);
|
||||
if (c.mFacingMode.IsString()) {
|
||||
if (c.mFacingMode.GetAsString() != deviceFacingMode) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
} else if (c.mFacingMode.IsStringSequence()) {
|
||||
if (!c.mFacingMode.GetAsStringSequence().Contains(deviceFacingMode)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
} else {
|
||||
if (!value.GetAsStringSequence().Contains(s)) {
|
||||
return false;
|
||||
auto& exact = c.mFacingMode.GetAsConstrainDOMStringParameters().mExact.Value();
|
||||
if (exact.IsString()) {
|
||||
if (exact.GetAsString() != deviceFacingMode) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
} else if (!exact.GetAsStringSequence().Contains(deviceFacingMode)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
}
|
||||
nsString s;
|
||||
GetMediaSource(s);
|
||||
if (s != c.mMediaSource) {
|
||||
return false;
|
||||
return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
// Forward request to underlying object to interrogate per-mode capabilities.
|
||||
return GetSource()->SatisfiesConstraintSets(aConstraintSets);
|
||||
return GetSource()->GetBestFitnessDistance(aConstraintSets);
|
||||
}
|
||||
|
||||
AudioDevice::AudioDevice(MediaEngineAudioSource* aSource)
|
||||
: MediaDevice(aSource) {}
|
||||
|
||||
bool
|
||||
AudioDevice::SatisfiesConstraintSets(
|
||||
uint32_t
|
||||
AudioDevice::GetBestFitnessDistance(
|
||||
const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
|
||||
{
|
||||
// TODO: Add audio-specific constraints
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -961,7 +970,10 @@ static void
|
|||
{
|
||||
nsTArray<nsRefPtr<typename DeviceType::Source> > sources;
|
||||
|
||||
(engine->*aEnumerate)(aConstraints.mMediaSourceEnumValue, &sources);
|
||||
MediaSourceEnum src = StringToEnum(dom::MediaSourceEnumValues::strings,
|
||||
aConstraints.mMediaSource,
|
||||
dom::MediaSourceEnum::Other);
|
||||
(engine->*aEnumerate)(src, &sources);
|
||||
/**
|
||||
* We're allowing multiple tabs to access the same camera for parity
|
||||
* with Chrome. See bug 811757 for some of the issues surrounding
|
||||
|
@ -984,60 +996,24 @@ static void
|
|||
// Apply constraints to the list of sources.
|
||||
|
||||
auto& c = aConstraints;
|
||||
if (c.mUnsupportedRequirement) {
|
||||
// Check upfront the names of required constraints that are unsupported for
|
||||
// this media-type. The spec requires these to fail, so getting them out of
|
||||
// the way early provides a necessary invariant for the remaining algorithm
|
||||
// which maximizes code-reuse by ignoring constraints of the other type
|
||||
// (specifically, SatisfiesConstraintSets is reused for the advanced algorithm
|
||||
// where the spec requires it to ignore constraints of the other type)
|
||||
return;
|
||||
}
|
||||
|
||||
// Now on to the actual algorithm: First apply required constraints.
|
||||
// First apply top-level constraints.
|
||||
|
||||
// Stack constraintSets that pass, starting with the required one, because the
|
||||
// whole stack must be re-satisfied each time a capability-set is ruled out
|
||||
// (this avoids storing state and pushing algorithm into the lower-level code).
|
||||
// (this avoids storing state or pushing algorithm into the lower-level code).
|
||||
nsTArray<const MediaTrackConstraintSet*> aggregateConstraints;
|
||||
aggregateConstraints.AppendElement(&c.mRequired);
|
||||
aggregateConstraints.AppendElement(&c);
|
||||
|
||||
for (uint32_t i = 0; i < candidateSet.Length();) {
|
||||
// Overloading instead of template specialization keeps things local
|
||||
if (!candidateSet[i]->SatisfiesConstraintSets(aggregateConstraints)) {
|
||||
if (candidateSet[i]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jib): Proper non-ordered handling of nonrequired constraints (907352)
|
||||
//
|
||||
// For now, put nonrequired constraints at tail of Advanced list.
|
||||
// This isn't entirely accurate, as order will matter, but few will notice
|
||||
// the difference until we get camera selection and a few more constraints.
|
||||
if (c.mNonrequired.Length()) {
|
||||
if (!c.mAdvanced.WasPassed()) {
|
||||
c.mAdvanced.Construct();
|
||||
}
|
||||
c.mAdvanced.Value().MoveElementsFrom(c.mNonrequired);
|
||||
}
|
||||
|
||||
// Then apply advanced (formerly known as optional) constraints.
|
||||
//
|
||||
// These are only effective when there are multiple sources to pick from.
|
||||
// Spec as-of-this-writing says to run algorithm on "all possible tracks
|
||||
// of media type T that the browser COULD RETURN" (emphasis added).
|
||||
//
|
||||
// We think users ultimately control which devices we could return, so after
|
||||
// determining the webpage's preferred list, we add the remaining choices
|
||||
// to the tail, reasoning that they would all have passed individually,
|
||||
// i.e. if the user had any one of them as their sole device (enabled).
|
||||
//
|
||||
// This avoids users having to unplug/disable devices should a webpage pick
|
||||
// the wrong one (UX-fail). Webpage-preferred devices will be listed first.
|
||||
|
||||
SourceSet tailSet;
|
||||
// Then apply advanced constraints.
|
||||
|
||||
if (c.mAdvanced.WasPassed()) {
|
||||
auto &array = c.mAdvanced.Value();
|
||||
|
@ -1046,24 +1022,20 @@ static void
|
|||
aggregateConstraints.AppendElement(&array[i]);
|
||||
SourceSet rejects;
|
||||
for (uint32_t j = 0; j < candidateSet.Length();) {
|
||||
if (!candidateSet[j]->SatisfiesConstraintSets(aggregateConstraints)) {
|
||||
if (candidateSet[j]->GetBestFitnessDistance(aggregateConstraints) == UINT32_MAX) {
|
||||
rejects.AppendElement(candidateSet[j]);
|
||||
candidateSet.RemoveElementAt(j);
|
||||
} else {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
(candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
|
||||
if (!candidateSet.Length()) {
|
||||
candidateSet.MoveElementsFrom(rejects);
|
||||
aggregateConstraints.RemoveElementAt(aggregateConstraints.Length() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Proper non-ordered handling of nonrequired constraints (Bug 907352)
|
||||
|
||||
aResult.MoveElementsFrom(candidateSet);
|
||||
aResult.MoveElementsFrom(tailSet);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1228,10 +1200,9 @@ public:
|
|||
MOZ_ASSERT(mOnSuccess);
|
||||
MOZ_ASSERT(mOnFailure);
|
||||
if (IsOn(mConstraints.mVideo)) {
|
||||
VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
|
||||
nsTArray<nsRefPtr<VideoDevice>> sources;
|
||||
GetSources(backend, constraints, &MediaEngine::EnumerateVideoDevices, sources);
|
||||
|
||||
GetSources(backend, GetInvariant(mConstraints.mVideo),
|
||||
&MediaEngine::EnumerateVideoDevices, sources);
|
||||
if (!sources.Length()) {
|
||||
Fail(NS_LITERAL_STRING("NotFoundError"));
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -1241,10 +1212,9 @@ public:
|
|||
LOG(("Selected video device"));
|
||||
}
|
||||
if (IsOn(mConstraints.mAudio)) {
|
||||
AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
|
||||
nsTArray<nsRefPtr<AudioDevice>> sources;
|
||||
GetSources(backend, constraints, &MediaEngine::EnumerateAudioDevices, sources);
|
||||
|
||||
GetSources(backend, GetInvariant(mConstraints.mAudio),
|
||||
&MediaEngine::EnumerateAudioDevices, sources);
|
||||
if (!sources.Length()) {
|
||||
Fail(NS_LITERAL_STRING("NotFoundError"));
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -1382,19 +1352,17 @@ public:
|
|||
|
||||
ScopedDeletePtr<SourceSet> final(new SourceSet);
|
||||
if (IsOn(mConstraints.mVideo)) {
|
||||
VideoTrackConstraintsN constraints(GetInvariant(mConstraints.mVideo));
|
||||
nsTArray<nsRefPtr<VideoDevice>> s;
|
||||
GetSources(backend, constraints, &MediaEngine::EnumerateVideoDevices, s,
|
||||
mLoopbackVideoDevice.get());
|
||||
GetSources(backend, GetInvariant(mConstraints.mVideo),
|
||||
&MediaEngine::EnumerateVideoDevices, s, mLoopbackVideoDevice.get());
|
||||
for (uint32_t i = 0; i < s.Length(); i++) {
|
||||
final->AppendElement(s[i]);
|
||||
}
|
||||
}
|
||||
if (IsOn(mConstraints.mAudio)) {
|
||||
AudioTrackConstraintsN constraints(GetInvariant(mConstraints.mAudio));
|
||||
nsTArray<nsRefPtr<AudioDevice>> s;
|
||||
GetSources(backend, constraints, &MediaEngine::EnumerateAudioDevices, s,
|
||||
mLoopbackAudioDevice.get());
|
||||
GetSources(backend, GetInvariant(mConstraints.mAudio),
|
||||
&MediaEngine::EnumerateAudioDevices, s, mLoopbackAudioDevice.get());
|
||||
for (uint32_t i = 0; i < s.Length(); i++) {
|
||||
final->AppendElement(s[i]);
|
||||
}
|
||||
|
@ -1629,23 +1597,6 @@ MediaManager::GetUserMedia(
|
|||
c.mVideo.SetAsBoolean() = false;
|
||||
}
|
||||
|
||||
if (c.mVideo.IsMediaTrackConstraints() && !privileged) {
|
||||
auto& tc = c.mVideo.GetAsMediaTrackConstraints();
|
||||
// only allow privileged content to set the window id
|
||||
if (tc.mBrowserWindow.WasPassed()) {
|
||||
tc.mBrowserWindow.Construct(-1);
|
||||
}
|
||||
|
||||
if (tc.mAdvanced.WasPassed()) {
|
||||
size_t length = tc.mAdvanced.Value().Length();
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (tc.mAdvanced.Value()[i].mBrowserWindow.WasPassed()) {
|
||||
tc.mAdvanced.Value()[i].mBrowserWindow.Construct(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pass callbacks and MediaStreamListener along to GetUserMediaTask.
|
||||
nsAutoPtr<GetUserMediaTask> task;
|
||||
if (c.mFake) {
|
||||
|
@ -1671,12 +1622,37 @@ MediaManager::GetUserMedia(
|
|||
privileged = true;
|
||||
}
|
||||
|
||||
|
||||
if (c.mVideo.IsMediaTrackConstraints()) {
|
||||
auto& tc = c.mVideo.GetAsMediaTrackConstraints();
|
||||
auto& vc = c.mVideo.GetAsMediaTrackConstraints();
|
||||
MediaSourceEnum src = StringToEnum(dom::MediaSourceEnumValues::strings,
|
||||
tc.mMediaSource,
|
||||
vc.mMediaSource,
|
||||
dom::MediaSourceEnum::Other);
|
||||
if (vc.mAdvanced.WasPassed()) {
|
||||
if (src != dom::MediaSourceEnum::Camera) {
|
||||
// iterate through advanced, forcing mediaSource to match "root"
|
||||
const char *camera = EnumToASCII(dom::MediaSourceEnumValues::strings,
|
||||
dom::MediaSourceEnum::Camera);
|
||||
for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
|
||||
if (cs.mMediaSource.EqualsASCII(camera)) {
|
||||
cs.mMediaSource = vc.mMediaSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!privileged) {
|
||||
// only allow privileged content to set the window id
|
||||
if (vc.mBrowserWindow.WasPassed()) {
|
||||
vc.mBrowserWindow.Construct(-1);
|
||||
}
|
||||
if (vc.mAdvanced.WasPassed()) {
|
||||
for (MediaTrackConstraintSet& cs : vc.mAdvanced.Value()) {
|
||||
if (cs.mBrowserWindow.WasPassed()) {
|
||||
cs.mBrowserWindow.Construct(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (src) {
|
||||
case dom::MediaSourceEnum::Camera:
|
||||
break;
|
||||
|
|
|
@ -523,7 +523,7 @@ public:
|
|||
explicit VideoDevice(Source* aSource);
|
||||
NS_IMETHOD GetType(nsAString& aType);
|
||||
Source* GetSource();
|
||||
bool SatisfiesConstraintSets(
|
||||
uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
|
||||
};
|
||||
|
||||
|
@ -535,7 +535,7 @@ public:
|
|||
explicit AudioDevice(Source* aSource);
|
||||
NS_IMETHOD GetType(nsAString& aType);
|
||||
Source* GetSource();
|
||||
bool SatisfiesConstraintSets(
|
||||
uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets);
|
||||
};
|
||||
|
||||
|
|
|
@ -926,9 +926,17 @@ PeerConnectionWrapper.prototype = {
|
|||
var type = '';
|
||||
if (constraints.audio) {
|
||||
type = 'audio';
|
||||
stream.getAudioTracks().map(track => {
|
||||
info(this + " gUM local stream " + stream.id +
|
||||
" with audio track " + track.id);
|
||||
});
|
||||
}
|
||||
if (constraints.video) {
|
||||
type += 'video';
|
||||
stream.getVideoTracks().map(track => {
|
||||
info(this + " gUM local stream " + stream.id +
|
||||
" with video track " + track.id);
|
||||
});
|
||||
}
|
||||
this.attachMedia(stream, type, 'local');
|
||||
});
|
||||
|
@ -1119,9 +1127,17 @@ PeerConnectionWrapper.prototype = {
|
|||
var type = '';
|
||||
if (event.stream.getAudioTracks().length > 0) {
|
||||
type = 'audio';
|
||||
event.stream.getAudioTracks().map(track => {
|
||||
info(this + " remote stream " + event.stream.id + " with audio track " +
|
||||
track.id);
|
||||
});
|
||||
}
|
||||
if (event.stream.getVideoTracks().length > 0) {
|
||||
type += 'video';
|
||||
event.stream.getVideoTracks().map(track => {
|
||||
info(this + " remote stream " + event.stream.id + " with video track " +
|
||||
track.id);
|
||||
});
|
||||
}
|
||||
this.attachMedia(event.stream, type, 'remote');
|
||||
});
|
||||
|
@ -1395,11 +1411,9 @@ PeerConnectionWrapper.prototype = {
|
|||
var checkSdpForMsids = (desc, streams, side) => {
|
||||
streams.forEach(stream => {
|
||||
stream.getTracks().forEach(track => {
|
||||
// TODO(bug 1089798): Once DOMMediaStream has an id field, we
|
||||
// should be verifying that the SDP contains
|
||||
// a=msid:<stream-id> <track-id>
|
||||
ok(desc.sdp.match(new RegExp("a=msid:[^ ]+ " + track.id)),
|
||||
side + " SDP contains track id " + track.id );
|
||||
ok(desc.sdp.match(new RegExp("a=msid:" + stream.id + " " + track.id)),
|
||||
this + ": " + side + " SDP contains stream " + stream.id +
|
||||
" and track " + track.id );
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -486,14 +486,6 @@ var commandsPeerConnectionOfferAnswer = [
|
|||
function PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT(test) {
|
||||
return test.pcRemote.checkMediaFlowPresent();
|
||||
},
|
||||
/* TODO: re-enable when Bug 1095218 lands
|
||||
function PC_LOCAL_CHECK_MSID(test) {
|
||||
test.pcLocal.checkMsids();
|
||||
},
|
||||
function PC_REMOTE_CHECK_MSID(test) {
|
||||
test.pcRemote.checkMsids();
|
||||
},
|
||||
*/
|
||||
function PC_LOCAL_CHECK_STATS(test) {
|
||||
return test.pcLocal.getStats(null).then(stats => {
|
||||
test.pcLocal.checkStats(stats, test.steeplechase);
|
||||
|
|
|
@ -15,58 +15,58 @@ createHTML({ title: "Test getUserMedia constraints", bug: "882145" });
|
|||
*/
|
||||
var tests = [
|
||||
// Each test here tests a different constraint or codepath.
|
||||
{ message: "unknown required constraint on video fails",
|
||||
constraints: { video: { somethingUnknown:0, require:["somethingUnknown"] },
|
||||
{ message: "unknown required constraint on video ignored",
|
||||
constraints: { video: { somethingUnknown: { exact: 0 } },
|
||||
fake: true },
|
||||
error: "NotFoundError" },
|
||||
{ message: "unknown required constraint on audio fails",
|
||||
constraints: { audio: { somethingUnknown:0, require:["somethingUnknown"] },
|
||||
error: null },
|
||||
{ message: "unknown required constraint on audio ignored",
|
||||
constraints: { audio: { somethingUnknown: { exact: 0 } },
|
||||
fake: true },
|
||||
error: "NotFoundError" },
|
||||
error: null },
|
||||
{ message: "video overconstrained by facingMode fails",
|
||||
constraints: { video: { facingMode:'left', require:["facingMode"] },
|
||||
constraints: { video: { facingMode:{ exact: 'left' } },
|
||||
fake: true },
|
||||
error: "NotFoundError" },
|
||||
{ message: "video overconstrained by facingMode array fails",
|
||||
constraints: { video: { facingMode:['left', 'right'], require:["facingMode"] },
|
||||
constraints: { video: { facingMode:{ exact: ['left', 'right'] } },
|
||||
fake: true },
|
||||
error: "NotFoundError" },
|
||||
{ message: "audio overconstrained by facingMode fails",
|
||||
constraints: { audio: { facingMode:'left', require:["facingMode"] },
|
||||
{ message: "audio overconstrained by facingMode ignored",
|
||||
constraints: { audio: { facingMode: { exact: 'left' } },
|
||||
fake: true },
|
||||
error: "NotFoundError" },
|
||||
error: null },
|
||||
{ message: "full screensharing requires permission",
|
||||
constraints: { video: { mediaSource:'screen' } },
|
||||
constraints: { video: { mediaSource: 'screen' } },
|
||||
error: "PermissionDeniedError" },
|
||||
{ message: "application screensharing requires permission",
|
||||
constraints: { video: { mediaSource:'application' } },
|
||||
constraints: { video: { mediaSource: 'application' } },
|
||||
error: "PermissionDeniedError" },
|
||||
{ message: "window screensharing requires permission",
|
||||
constraints: { video: { mediaSource:'window' } },
|
||||
constraints: { video: { mediaSource: 'window' } },
|
||||
error: "PermissionDeniedError" },
|
||||
{ message: "browser screensharing requires permission",
|
||||
constraints: { video: { mediaSource:'browser' } },
|
||||
constraints: { video: { mediaSource: 'browser' } },
|
||||
error: "PermissionDeniedError" },
|
||||
{ message: "unknown mediaSource fails",
|
||||
constraints: { video: { mediaSource:'uncle' } },
|
||||
constraints: { video: { mediaSource: 'uncle' } },
|
||||
error: "NotFoundError" },
|
||||
{ message: "Success-path: optional video facingMode + audio ignoring facingMode",
|
||||
constraints: { fake: true,
|
||||
audio: { mediaSource:'microphone',
|
||||
facingMode:'left',
|
||||
foo:0,
|
||||
advanced: [{ facingMode:'environment' },
|
||||
{ facingMode:'user' },
|
||||
{ bar:0 }] },
|
||||
video: { mediaSource:'camera',
|
||||
facingMode:['left', 'right', 'user', 'environment'],
|
||||
foo:0,
|
||||
advanced: [{ facingMode:'environment' },
|
||||
{ facingMode:['user'] },
|
||||
{ bar:0 }] } },
|
||||
audio: { mediaSource: 'microphone',
|
||||
facingMode: 'left',
|
||||
foo: 0,
|
||||
advanced: [{ facingMode: 'environment' },
|
||||
{ facingMode: 'user' },
|
||||
{ bar: 0 }] },
|
||||
video: { mediaSource: 'camera',
|
||||
foo: 0,
|
||||
advanced: [{ facingMode: 'environment' },
|
||||
{ facingMode: ['user'] },
|
||||
{ facingMode: ['left', 'right', 'user'] },
|
||||
{ bar: 0 }] } },
|
||||
error: null },
|
||||
{ message: "legacy facingMode ignored",
|
||||
constraints: { video: { mandatory: { facingMode:'left' } }, fake: true },
|
||||
constraints: { video: { mandatory: { facingMode: 'left' } }, fake: true },
|
||||
error: null },
|
||||
];
|
||||
|
||||
|
|
|
@ -18,9 +18,6 @@ namespace dom {
|
|||
class File;
|
||||
}
|
||||
|
||||
struct VideoTrackConstraintsN;
|
||||
struct AudioTrackConstraintsN;
|
||||
|
||||
/**
|
||||
* Abstract interface for managing audio and video devices. Each platform
|
||||
* must implement a concrete class that will map these classes and methods
|
||||
|
@ -219,10 +216,10 @@ public:
|
|||
virtual ~MediaEngineVideoSource() {}
|
||||
|
||||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const VideoTrackConstraintsN &aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) = 0;
|
||||
|
||||
virtual bool SatisfiesConstraintSets(
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) = 0;
|
||||
|
||||
protected:
|
||||
|
@ -241,7 +238,7 @@ public:
|
|||
virtual ~MediaEngineAudioSource() {}
|
||||
|
||||
/* This call reserves but does not start the device. */
|
||||
virtual nsresult Allocate(const AudioTrackConstraintsN &aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) = 0;
|
||||
protected:
|
||||
explicit MediaEngineAudioSource(MediaEngineState aState)
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
|
||||
#include "MediaEngineCameraVideoSource.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using dom::OwningLongOrConstrainLongRange;
|
||||
using dom::ConstrainLongRange;
|
||||
using dom::OwningDoubleOrConstrainDoubleRange;
|
||||
using dom::ConstrainDoubleRange;
|
||||
using dom::MediaTrackConstraintSet;
|
||||
|
||||
|
@ -20,34 +24,6 @@ extern PRLogModuleInfo* GetMediaManagerLog();
|
|||
#define LOGFRAME(msg)
|
||||
#endif
|
||||
|
||||
/* static */ bool
|
||||
MediaEngineCameraVideoSource::IsWithin(int32_t n, const ConstrainLongRange& aRange) {
|
||||
return aRange.mMin <= n && n <= aRange.mMax;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
MediaEngineCameraVideoSource::IsWithin(double n, const ConstrainDoubleRange& aRange) {
|
||||
return aRange.mMin <= n && n <= aRange.mMax;
|
||||
}
|
||||
|
||||
/* static */ int32_t
|
||||
MediaEngineCameraVideoSource::Clamp(int32_t n, const ConstrainLongRange& aRange) {
|
||||
return std::max(aRange.mMin, std::min(n, aRange.mMax));
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
MediaEngineCameraVideoSource::AreIntersecting(const ConstrainLongRange& aA, const ConstrainLongRange& aB) {
|
||||
return aA.mMax >= aB.mMin && aA.mMin <= aB.mMax;
|
||||
}
|
||||
|
||||
/* static */ bool
|
||||
MediaEngineCameraVideoSource::Intersect(ConstrainLongRange& aA, const ConstrainLongRange& aB) {
|
||||
MOZ_ASSERT(AreIntersecting(aA, aB));
|
||||
aA.mMin = std::max(aA.mMin, aB.mMin);
|
||||
aA.mMax = std::min(aA.mMax, aB.mMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
// guts for appending data to the MSG track
|
||||
bool MediaEngineCameraVideoSource::AppendToTrack(SourceMediaStream* aSource,
|
||||
layers::Image* aImage,
|
||||
|
@ -68,84 +44,246 @@ bool MediaEngineCameraVideoSource::AppendToTrack(SourceMediaStream* aSource,
|
|||
return aSource->AppendToTrack(aID, &(segment));
|
||||
}
|
||||
|
||||
// A special version of the algorithm for cameras that don't list capabilities.
|
||||
void
|
||||
MediaEngineCameraVideoSource::GuessCapability(
|
||||
const VideoTrackConstraintsN& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs)
|
||||
// Sub-classes (B2G or desktop) should overload one of both of these two methods
|
||||
// to provide capabilities
|
||||
size_t
|
||||
MediaEngineCameraVideoSource::NumCapabilities()
|
||||
{
|
||||
LOG(("GuessCapability: prefs: %dx%d @%d-%dfps",
|
||||
return mHardcodedCapabilities.Length();
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineCameraVideoSource::GetCapability(size_t aIndex,
|
||||
webrtc::CaptureCapability& aOut)
|
||||
{
|
||||
MOZ_ASSERT(aIndex < mHardcodedCapabilities.Length());
|
||||
aOut = mHardcodedCapabilities[aIndex];
|
||||
}
|
||||
|
||||
// The full algorithm for all cameras. Sources that don't list capabilities
|
||||
// need to fake it and hardcode some by populating mHardcodedCapabilities above.
|
||||
|
||||
// Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
|
||||
|
||||
template<class ValueType, class ConstrainRange>
|
||||
/* static */ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(ValueType n,
|
||||
const ConstrainRange& aRange)
|
||||
{
|
||||
if ((aRange.mExact.WasPassed() && aRange.mExact.Value() != n) ||
|
||||
(aRange.mMin.WasPassed() && aRange.mMin.Value() > n) ||
|
||||
(aRange.mMax.WasPassed() && aRange.mMax.Value() < n)) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
if (!aRange.mIdeal.WasPassed() || n == aRange.mIdeal.Value()) {
|
||||
return 0;
|
||||
}
|
||||
return uint32_t(ValueType((std::abs(n - aRange.mIdeal.Value()) * 1000) /
|
||||
std::max(std::abs(n), std::abs(aRange.mIdeal.Value()))));
|
||||
}
|
||||
|
||||
// Binding code doesn't templatize well...
|
||||
|
||||
template<>
|
||||
/* static */ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(int32_t n,
|
||||
const OwningLongOrConstrainLongRange& aConstraint)
|
||||
{
|
||||
if (aConstraint.IsLong()) {
|
||||
ConstrainLongRange range;
|
||||
range.mIdeal.Construct(aConstraint.GetAsLong());
|
||||
return FitnessDistance(n, range);
|
||||
} else {
|
||||
return FitnessDistance(n, aConstraint.GetAsConstrainLongRange());
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
/* static */ uint32_t
|
||||
MediaEngineCameraVideoSource::FitnessDistance(double n,
|
||||
const OwningDoubleOrConstrainDoubleRange& aConstraint)
|
||||
{
|
||||
if (aConstraint.IsDouble()) {
|
||||
ConstrainDoubleRange range;
|
||||
range.mIdeal.Construct(aConstraint.GetAsDouble());
|
||||
return FitnessDistance(n, range);
|
||||
} else {
|
||||
return FitnessDistance(n, aConstraint.GetAsConstrainDoubleRange());
|
||||
}
|
||||
}
|
||||
|
||||
/*static*/ uint32_t
|
||||
MediaEngineCameraVideoSource::GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
|
||||
const MediaTrackConstraintSet &aConstraints)
|
||||
{
|
||||
uint64_t distance =
|
||||
uint64_t(FitnessDistance(int32_t(aCandidate.width), aConstraints.mWidth)) +
|
||||
uint64_t(FitnessDistance(int32_t(aCandidate.height), aConstraints.mHeight)) +
|
||||
uint64_t(FitnessDistance(double(aCandidate.maxFPS), aConstraints.mFrameRate));
|
||||
return uint32_t(std::min(distance, uint64_t(UINT32_MAX)));
|
||||
}
|
||||
|
||||
// Find best capability by removing inferiors. May leave >1 of equal distance
|
||||
|
||||
/* static */ void
|
||||
MediaEngineCameraVideoSource::TrimLessFitCandidates(CapabilitySet& set) {
|
||||
uint32_t best = UINT32_MAX;
|
||||
for (auto& candidate : set) {
|
||||
if (best > candidate.mDistance) {
|
||||
best = candidate.mDistance;
|
||||
}
|
||||
}
|
||||
for (size_t i = 0; i < set.Length();) {
|
||||
if (set[i].mDistance > best) {
|
||||
set.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
MOZ_ASSERT(set.Length());
|
||||
}
|
||||
|
||||
// GetBestFitnessDistance returns the best distance the capture device can offer
|
||||
// as a whole, given an accumulated number of ConstraintSets.
|
||||
// Ideal values are considered in the first ConstraintSet only.
|
||||
// Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
|
||||
// A finite result may be used to calculate this device's ranking as a choice.
|
||||
|
||||
uint32_t
|
||||
MediaEngineCameraVideoSource::GetBestFitnessDistance(
|
||||
const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
|
||||
{
|
||||
size_t num = NumCapabilities();
|
||||
|
||||
CapabilitySet candidateSet;
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
candidateSet.AppendElement(i);
|
||||
}
|
||||
|
||||
bool first = true;
|
||||
for (const MediaTrackConstraintSet* cs : aConstraintSets) {
|
||||
for (size_t i = 0; i < candidateSet.Length(); ) {
|
||||
auto& candidate = candidateSet[i];
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
uint32_t distance = GetFitnessDistance(cap, *cs);
|
||||
if (distance == UINT32_MAX) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
if (first) {
|
||||
candidate.mDistance = distance;
|
||||
}
|
||||
}
|
||||
}
|
||||
first = false;
|
||||
}
|
||||
if (!candidateSet.Length()) {
|
||||
return UINT32_MAX;
|
||||
}
|
||||
TrimLessFitCandidates(candidateSet);
|
||||
return candidateSet[0].mDistance;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaEngineCameraVideoSource::ChooseCapability(
|
||||
const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
{
|
||||
LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
|
||||
aPrefs.mWidth, aPrefs.mHeight, aPrefs.mFPS, aPrefs.mMinFPS));
|
||||
|
||||
// In short: compound constraint-ranges and use pref as ideal.
|
||||
size_t num = NumCapabilities();
|
||||
|
||||
ConstrainLongRange cWidth(aConstraints.mRequired.mWidth);
|
||||
ConstrainLongRange cHeight(aConstraints.mRequired.mHeight);
|
||||
CapabilitySet candidateSet;
|
||||
for (size_t i = 0; i < num; i++) {
|
||||
candidateSet.AppendElement(i);
|
||||
}
|
||||
|
||||
// First, filter capabilities by required constraints (min, max, exact).
|
||||
|
||||
for (size_t i = 0; i < candidateSet.Length();) {
|
||||
auto& candidate = candidateSet[i];
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
candidate.mDistance = GetFitnessDistance(cap, aConstraints);
|
||||
if (candidate.mDistance == UINT32_MAX) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
// Filter further with all advanced constraints (that don't overconstrain).
|
||||
|
||||
if (aConstraints.mAdvanced.WasPassed()) {
|
||||
const auto& advanced = aConstraints.mAdvanced.Value();
|
||||
for (uint32_t i = 0; i < advanced.Length(); i++) {
|
||||
if (AreIntersecting(cWidth, advanced[i].mWidth) &&
|
||||
AreIntersecting(cHeight, advanced[i].mHeight)) {
|
||||
Intersect(cWidth, advanced[i].mWidth);
|
||||
Intersect(cHeight, advanced[i].mHeight);
|
||||
for (const MediaTrackConstraintSet &cs : aConstraints.mAdvanced.Value()) {
|
||||
CapabilitySet rejects;
|
||||
for (size_t i = 0; i < candidateSet.Length();) {
|
||||
auto& candidate = candidateSet[i];
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
if (GetFitnessDistance(cap, cs) == UINT32_MAX) {
|
||||
rejects.AppendElement(candidate);
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
if (!candidateSet.Length()) {
|
||||
candidateSet.MoveElementsFrom(rejects);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Detect Mac HD cams and give them some love in the form of a dynamic default
|
||||
// since that hardware switches between 4:3 at low res and 16:9 at higher res.
|
||||
//
|
||||
// Logic is: if we're relying on defaults in aPrefs, then
|
||||
// only use HD pref when non-HD pref is too small and HD pref isn't too big.
|
||||
if (!candidateSet.Length()) {
|
||||
LOG(("failed to find capability match from %d choices",num));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool macHD = ((!aPrefs.mWidth || !aPrefs.mHeight) &&
|
||||
mDeviceName.EqualsASCII("FaceTime HD Camera (Built-in)") &&
|
||||
(aPrefs.GetWidth() < cWidth.mMin ||
|
||||
aPrefs.GetHeight() < cHeight.mMin) &&
|
||||
!(aPrefs.GetWidth(true) > cWidth.mMax ||
|
||||
aPrefs.GetHeight(true) > cHeight.mMax));
|
||||
int prefWidth = aPrefs.GetWidth(macHD);
|
||||
int prefHeight = aPrefs.GetHeight(macHD);
|
||||
// Remaining algorithm is up to the UA.
|
||||
|
||||
// Clamp width and height without distorting inherent aspect too much.
|
||||
TrimLessFitCandidates(candidateSet);
|
||||
|
||||
if (IsWithin(prefWidth, cWidth) == IsWithin(prefHeight, cHeight)) {
|
||||
// If both are within, we get the default (pref) aspect.
|
||||
// If neither are within, we get the aspect of the enclosing constraint.
|
||||
// Either are presumably reasonable (presuming constraints are sane).
|
||||
mCapability.width = Clamp(prefWidth, cWidth);
|
||||
mCapability.height = Clamp(prefHeight, cHeight);
|
||||
} else {
|
||||
// But if only one clips (e.g. width), the resulting skew is undesirable:
|
||||
// .------------.
|
||||
// | constraint |
|
||||
// .----+------------+----.
|
||||
// | | | |
|
||||
// |pref| result | | prefAspect != resultAspect
|
||||
// | | | |
|
||||
// '----+------------+----'
|
||||
// '------------'
|
||||
// So in this case, preserve prefAspect instead:
|
||||
// .------------.
|
||||
// | constraint |
|
||||
// .------------.
|
||||
// |pref | prefAspect is unchanged
|
||||
// '------------'
|
||||
// | |
|
||||
// '------------'
|
||||
if (IsWithin(prefWidth, cWidth)) {
|
||||
mCapability.height = Clamp(prefHeight, cHeight);
|
||||
mCapability.width = Clamp((mCapability.height * prefWidth) /
|
||||
prefHeight, cWidth);
|
||||
} else {
|
||||
mCapability.width = Clamp(prefWidth, cWidth);
|
||||
mCapability.height = Clamp((mCapability.width * prefHeight) /
|
||||
prefWidth, cHeight);
|
||||
// Any remaining multiples all have the same distance. A common case of this
|
||||
// occurs when no ideal is specified. Lean toward defaults.
|
||||
{
|
||||
MediaTrackConstraintSet prefs;
|
||||
prefs.mWidth.SetAsLong() = aPrefs.GetWidth();
|
||||
prefs.mHeight.SetAsLong() = aPrefs.GetHeight();
|
||||
prefs.mFrameRate.SetAsDouble() = aPrefs.mMinFPS;
|
||||
|
||||
for (auto& candidate : candidateSet) {
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
candidate.mDistance = GetFitnessDistance(cap, prefs);
|
||||
}
|
||||
TrimLessFitCandidates(candidateSet);
|
||||
}
|
||||
|
||||
// Any remaining multiples all have the same distance, but may vary on
|
||||
// format. Some formats are more desirable for certain use like WebRTC.
|
||||
// E.g. I420 over RGB24 can remove a needless format conversion.
|
||||
|
||||
bool found = false;
|
||||
for (auto& candidate : candidateSet) {
|
||||
webrtc::CaptureCapability cap;
|
||||
GetCapability(candidate.mIndex, cap);
|
||||
if (cap.rawType == webrtc::RawVideoType::kVideoI420 ||
|
||||
cap.rawType == webrtc::RawVideoType::kVideoYUY2 ||
|
||||
cap.rawType == webrtc::RawVideoType::kVideoYV12) {
|
||||
mCapability = cap;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mCapability.maxFPS = MediaEngine::DEFAULT_VIDEO_FPS;
|
||||
LOG(("chose cap %dx%d @%dfps",
|
||||
mCapability.width, mCapability.height, mCapability.maxFPS));
|
||||
if (!found) {
|
||||
GetCapability(candidateSet[0].mIndex, mCapability);
|
||||
}
|
||||
|
||||
LOG(("chose cap %dx%d @%dfps codec %d raw %d",
|
||||
mCapability.width, mCapability.height, mCapability.maxFPS,
|
||||
mCapability.codecType, mCapability.rawType));
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
@ -58,7 +58,19 @@ public:
|
|||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
struct CapabilityCandidate {
|
||||
explicit CapabilityCandidate(uint8_t index, uint32_t distance = 0)
|
||||
: mIndex(index), mDistance(distance) {}
|
||||
|
||||
size_t mIndex;
|
||||
uint32_t mDistance;
|
||||
};
|
||||
typedef nsTArray<CapabilityCandidate> CapabilitySet;
|
||||
|
||||
~MediaEngineCameraVideoSource() {}
|
||||
|
||||
// guts for appending data to the MSG track
|
||||
|
@ -66,15 +78,15 @@ protected:
|
|||
layers::Image* aImage,
|
||||
TrackID aID,
|
||||
StreamTime delta);
|
||||
|
||||
static bool IsWithin(int32_t n, const dom::ConstrainLongRange& aRange);
|
||||
static bool IsWithin(double n, const dom::ConstrainDoubleRange& aRange);
|
||||
static int32_t Clamp(int32_t n, const dom::ConstrainLongRange& aRange);
|
||||
static bool AreIntersecting(const dom::ConstrainLongRange& aA,
|
||||
const dom::ConstrainLongRange& aB);
|
||||
static bool Intersect(dom::ConstrainLongRange& aA, const dom::ConstrainLongRange& aB);
|
||||
void GuessCapability(const VideoTrackConstraintsN& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs);
|
||||
template<class ValueType, class ConstrainRange>
|
||||
static uint32_t FitnessDistance(ValueType n, const ConstrainRange& aRange);
|
||||
static uint32_t GetFitnessDistance(const webrtc::CaptureCapability& aCandidate,
|
||||
const dom::MediaTrackConstraintSet &aConstraints);
|
||||
static void TrimLessFitCandidates(CapabilitySet& set);
|
||||
virtual size_t NumCapabilities();
|
||||
virtual void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut);
|
||||
bool ChooseCapability(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs);
|
||||
|
||||
// Engine variables.
|
||||
|
||||
|
@ -101,6 +113,7 @@ protected:
|
|||
|
||||
webrtc::CaptureCapability mCapability; // Doesn't work on OS X.
|
||||
|
||||
nsTArray<webrtc::CaptureCapability> mHardcodedCapabilities; // For OSX & B2G
|
||||
nsString mDeviceName;
|
||||
nsString mUniqueId;
|
||||
};
|
||||
|
|
|
@ -69,7 +69,7 @@ MediaEngineDefaultVideoSource::GetUUID(nsAString& aUUID)
|
|||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineDefaultVideoSource::Allocate(const VideoTrackConstraintsN &aConstraints,
|
||||
MediaEngineDefaultVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
{
|
||||
if (mState != kReleased) {
|
||||
|
@ -349,7 +349,7 @@ MediaEngineDefaultAudioSource::GetUUID(nsAString& aUUID)
|
|||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineDefaultAudioSource::Allocate(const AudioTrackConstraintsN &aConstraints,
|
||||
MediaEngineDefaultAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
{
|
||||
if (mState != kReleased) {
|
||||
|
|
|
@ -40,7 +40,7 @@ public:
|
|||
virtual void GetName(nsAString&) MOZ_OVERRIDE;
|
||||
virtual void GetUUID(nsAString&) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsresult Allocate(const VideoTrackConstraintsN &aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) MOZ_OVERRIDE;
|
||||
virtual nsresult Deallocate() MOZ_OVERRIDE;
|
||||
virtual nsresult Start(SourceMediaStream*, TrackID) MOZ_OVERRIDE;
|
||||
|
@ -54,7 +54,7 @@ public:
|
|||
SourceMediaStream *aSource,
|
||||
TrackID aId,
|
||||
StreamTime aDesiredTime) MOZ_OVERRIDE;
|
||||
virtual bool SatisfiesConstraintSets(
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) MOZ_OVERRIDE
|
||||
{
|
||||
return true;
|
||||
|
@ -108,7 +108,7 @@ public:
|
|||
virtual void GetName(nsAString&) MOZ_OVERRIDE;
|
||||
virtual void GetUUID(nsAString&) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsresult Allocate(const AudioTrackConstraintsN &aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) MOZ_OVERRIDE;
|
||||
virtual nsresult Deallocate() MOZ_OVERRIDE;
|
||||
virtual nsresult Start(SourceMediaStream*, TrackID) MOZ_OVERRIDE;
|
||||
|
|
|
@ -107,15 +107,51 @@ MediaEngineGonkVideoSource::NotifyPull(MediaStreamGraph* aGraph,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineGonkVideoSource::ChooseCapability(const VideoTrackConstraintsN& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs)
|
||||
size_t
|
||||
MediaEngineGonkVideoSource::NumCapabilities()
|
||||
{
|
||||
return GuessCapability(aConstraints, aPrefs);
|
||||
// TODO: Stop hardcoding. Use GetRecorderProfiles+GetProfileInfo (Bug 1128550)
|
||||
//
|
||||
// The camera-selecting constraints algorithm needs a set of capabilities to
|
||||
// work on. In lieu of something better, here are some generic values based on
|
||||
// http://en.wikipedia.org/wiki/Comparison_of_Firefox_OS_devices on Jan 2015.
|
||||
// When unknown, better overdo it with choices to not block legitimate asks.
|
||||
// TODO: Match with actual hardware or add code to query hardware.
|
||||
|
||||
if (mHardcodedCapabilities.IsEmpty()) {
|
||||
const struct { int width, height; } hardcodes[] = {
|
||||
{ 800, 1280 },
|
||||
{ 720, 1280 },
|
||||
{ 600, 1024 },
|
||||
{ 540, 960 },
|
||||
{ 480, 854 },
|
||||
{ 480, 800 },
|
||||
{ 320, 480 },
|
||||
{ 240, 320 }, // sole mode supported by emulator on try
|
||||
};
|
||||
const int framerates[] = { 15, 30 };
|
||||
|
||||
for (auto& hardcode : hardcodes) {
|
||||
webrtc::CaptureCapability c;
|
||||
c.width = hardcode.width;
|
||||
c.height = hardcode.height;
|
||||
for (int framerate : framerates) {
|
||||
c.maxFPS = framerate;
|
||||
mHardcodedCapabilities.AppendElement(c); // portrait
|
||||
}
|
||||
c.width = hardcode.height;
|
||||
c.height = hardcode.width;
|
||||
for (int framerate : framerates) {
|
||||
c.maxFPS = framerate;
|
||||
mHardcodedCapabilities.AppendElement(c); // landscape
|
||||
}
|
||||
}
|
||||
}
|
||||
return mHardcodedCapabilities.Length();
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineGonkVideoSource::Allocate(const VideoTrackConstraintsN& aConstraints,
|
||||
MediaEngineGonkVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs)
|
||||
{
|
||||
LOG((__FUNCTION__));
|
||||
|
|
|
@ -60,7 +60,7 @@ public:
|
|||
Init();
|
||||
}
|
||||
|
||||
virtual nsresult Allocate(const VideoTrackConstraintsN &aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs) MOZ_OVERRIDE;
|
||||
virtual nsresult Deallocate() MOZ_OVERRIDE;
|
||||
virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) MOZ_OVERRIDE;
|
||||
|
@ -69,11 +69,6 @@ public:
|
|||
SourceMediaStream* aSource,
|
||||
TrackID aId,
|
||||
StreamTime aDesiredTime) MOZ_OVERRIDE;
|
||||
virtual bool SatisfiesConstraintSets(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnHardwareStateChange(HardwareState aState, nsresult aReason) MOZ_OVERRIDE;
|
||||
void GetRotation();
|
||||
|
@ -109,8 +104,7 @@ protected:
|
|||
// Initialize the needed Video engine interfaces.
|
||||
void Init();
|
||||
void Shutdown();
|
||||
void ChooseCapability(const VideoTrackConstraintsN& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs);
|
||||
size_t NumCapabilities() MOZ_OVERRIDE;
|
||||
// Initialize the recording frame (MediaBuffer) callback and Gonk camera.
|
||||
// MediaBuffer will transfers to MediaStreamGraph via AppendToTrack.
|
||||
nsresult InitDirectMediaBuffer();
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
namespace mozilla {
|
||||
|
||||
using namespace mozilla::gfx;
|
||||
using dom::ConstrainLongRange;
|
||||
|
||||
NS_IMPL_ISUPPORTS(MediaEngineTabVideoSource, nsIDOMEventListener, nsITimerCallback)
|
||||
|
||||
|
@ -116,45 +115,26 @@ MediaEngineTabVideoSource::GetUUID(nsAString_internal& aUuid)
|
|||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineTabVideoSource::Allocate(const VideoTrackConstraintsN& aConstraints,
|
||||
MediaEngineTabVideoSource::Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs)
|
||||
{
|
||||
// windowId and scrollWithPage are not proper constraints, so just read them.
|
||||
// They have no well-defined behavior in advanced, so ignore them there.
|
||||
|
||||
ConstrainLongRange cWidth(aConstraints.mRequired.mWidth);
|
||||
ConstrainLongRange cHeight(aConstraints.mRequired.mHeight);
|
||||
mWindowId = aConstraints.mBrowserWindow.WasPassed() ?
|
||||
aConstraints.mBrowserWindow.Value() : -1;
|
||||
mScrollWithPage = aConstraints.mScrollWithPage.WasPassed() ?
|
||||
aConstraints.mScrollWithPage.Value() : true;
|
||||
|
||||
mWindowId = aConstraints.mBrowserWindow.WasPassed() ? aConstraints.mBrowserWindow.Value() : -1;
|
||||
bool haveScrollWithPage = aConstraints.mScrollWithPage.WasPassed();
|
||||
mScrollWithPage = haveScrollWithPage ? aConstraints.mScrollWithPage.Value() : true;
|
||||
FlattenedConstraints c(aConstraints);
|
||||
|
||||
if (aConstraints.mAdvanced.WasPassed()) {
|
||||
const auto& advanced = aConstraints.mAdvanced.Value();
|
||||
for (uint32_t i = 0; i < advanced.Length(); i++) {
|
||||
if (cWidth.mMax >= advanced[i].mWidth.mMin && cWidth.mMin <= advanced[i].mWidth.mMax &&
|
||||
cHeight.mMax >= advanced[i].mHeight.mMin && cHeight.mMin <= advanced[i].mHeight.mMax) {
|
||||
cWidth.mMin = std::max(cWidth.mMin, advanced[i].mWidth.mMin);
|
||||
cHeight.mMin = std::max(cHeight.mMin, advanced[i].mHeight.mMin);
|
||||
cWidth.mMax = std::min(cWidth.mMax, advanced[i].mWidth.mMax);
|
||||
cHeight.mMax = std::min(cHeight.mMax, advanced[i].mHeight.mMax);
|
||||
}
|
||||
|
||||
if (mWindowId == -1 && advanced[i].mBrowserWindow.WasPassed()) {
|
||||
mWindowId = advanced[i].mBrowserWindow.Value();
|
||||
}
|
||||
|
||||
if (!haveScrollWithPage && advanced[i].mScrollWithPage.WasPassed()) {
|
||||
mScrollWithPage = advanced[i].mScrollWithPage.Value();
|
||||
haveScrollWithPage = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ConstrainLongRange defaultRange;
|
||||
|
||||
mBufWidthMax = defaultRange.mMax > cWidth.mMax ? cWidth.mMax : aPrefs.GetWidth(false);
|
||||
mBufHeightMax = defaultRange.mMax > cHeight.mMax ? cHeight.mMax : aPrefs.GetHeight(false);
|
||||
|
||||
mTimePerFrame = aPrefs.mFPS ? 1000 / aPrefs.mFPS : aPrefs.mFPS;
|
||||
mBufWidthMax = c.mWidth.Clamp(c.mWidth.mIdeal.WasPassed() ?
|
||||
c.mWidth.mIdeal.Value() : aPrefs.GetWidth(false));
|
||||
mBufHeightMax = c.mHeight.Clamp(c.mHeight.mIdeal.WasPassed() ?
|
||||
c.mHeight.mIdeal.Value() : aPrefs.GetHeight(false));
|
||||
double frameRate = c.mFrameRate.Clamp(c.mFrameRate.mIdeal.WasPassed() ?
|
||||
c.mFrameRate.mIdeal.Value() : aPrefs.mFPS);
|
||||
mTimePerFrame = std::max(10, int(1000.0 / (frameRate > 0? frameRate : 1)));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
|||
|
||||
virtual void GetName(nsAString_internal&) MOZ_OVERRIDE;
|
||||
virtual void GetUUID(nsAString_internal&) MOZ_OVERRIDE;
|
||||
virtual nsresult Allocate(const VideoTrackConstraintsN &,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints &,
|
||||
const mozilla::MediaEnginePrefs&) MOZ_OVERRIDE;
|
||||
virtual nsresult Deallocate() MOZ_OVERRIDE;
|
||||
virtual nsresult Start(mozilla::SourceMediaStream*, mozilla::TrackID) MOZ_OVERRIDE;
|
||||
|
@ -32,10 +32,10 @@ class MediaEngineTabVideoSource : public MediaEngineVideoSource, nsIDOMEventList
|
|||
virtual const dom::MediaSourceEnum GetMediaSource() MOZ_OVERRIDE {
|
||||
return dom::MediaSourceEnum::Browser;
|
||||
}
|
||||
virtual bool SatisfiesConstraintSets(
|
||||
virtual uint32_t GetBestFitnessDistance(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) MOZ_OVERRIDE
|
||||
{
|
||||
return true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual nsresult TakePhoto(PhotoCallback* aCallback) MOZ_OVERRIDE
|
||||
|
|
|
@ -89,7 +89,7 @@ public:
|
|||
Init();
|
||||
}
|
||||
|
||||
virtual nsresult Allocate(const VideoTrackConstraintsN& aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs) MOZ_OVERRIDE;
|
||||
virtual nsresult Deallocate() MOZ_OVERRIDE;
|
||||
virtual nsresult Start(SourceMediaStream*, TrackID) MOZ_OVERRIDE;
|
||||
|
@ -109,9 +109,6 @@ public:
|
|||
|
||||
void Refresh(int aIndex);
|
||||
|
||||
bool SatisfiesConstraintSets(
|
||||
const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets) MOZ_OVERRIDE;
|
||||
|
||||
protected:
|
||||
~MediaEngineWebRTCVideoSource() { Shutdown(); }
|
||||
|
||||
|
@ -129,10 +126,8 @@ private:
|
|||
int mMinFps; // Min rate we want to accept
|
||||
dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
|
||||
|
||||
static bool SatisfiesConstraintSet(const dom::MediaTrackConstraintSet& aConstraints,
|
||||
const webrtc::CaptureCapability& aCandidate);
|
||||
void ChooseCapability(const VideoTrackConstraintsN& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs);
|
||||
size_t NumCapabilities() MOZ_OVERRIDE;
|
||||
void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) MOZ_OVERRIDE;
|
||||
};
|
||||
|
||||
class MediaEngineWebRTCAudioSource : public MediaEngineAudioSource,
|
||||
|
@ -165,7 +160,7 @@ public:
|
|||
virtual void GetName(nsAString& aName) MOZ_OVERRIDE;
|
||||
virtual void GetUUID(nsAString& aUUID) MOZ_OVERRIDE;
|
||||
|
||||
virtual nsresult Allocate(const AudioTrackConstraintsN& aConstraints,
|
||||
virtual nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
|
||||
const MediaEnginePrefs& aPrefs) MOZ_OVERRIDE;
|
||||
virtual nsresult Deallocate() MOZ_OVERRIDE;
|
||||
virtual nsresult Start(SourceMediaStream* aStream, TrackID aID) MOZ_OVERRIDE;
|
||||
|
|
|
@ -265,7 +265,7 @@ MediaEngineWebRTCAudioSource::Config(bool aEchoOn, uint32_t aEcho,
|
|||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineWebRTCAudioSource::Allocate(const AudioTrackConstraintsN &aConstraints,
|
||||
MediaEngineWebRTCAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
{
|
||||
if (mState == kReleased) {
|
||||
|
|
|
@ -153,176 +153,53 @@ MediaEngineWebRTCVideoSource::NotifyPull(MediaStreamGraph* aGraph,
|
|||
}
|
||||
}
|
||||
|
||||
/*static*/
|
||||
bool
|
||||
MediaEngineWebRTCVideoSource::SatisfiesConstraintSet(const MediaTrackConstraintSet &aConstraints,
|
||||
const webrtc::CaptureCapability& aCandidate) {
|
||||
if (!MediaEngineCameraVideoSource::IsWithin(aCandidate.width, aConstraints.mWidth) ||
|
||||
!MediaEngineCameraVideoSource::IsWithin(aCandidate.height, aConstraints.mHeight)) {
|
||||
return false;
|
||||
}
|
||||
if (!MediaEngineCameraVideoSource::IsWithin(aCandidate.maxFPS, aConstraints.mFrameRate)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef nsTArray<uint8_t> CapabilitySet;
|
||||
|
||||
// SatisfiesConstraintSets (plural) answers for the capture device as a whole
|
||||
// whether it can satisfy an accumulated number of capabilitySets.
|
||||
|
||||
bool
|
||||
MediaEngineWebRTCVideoSource::SatisfiesConstraintSets(
|
||||
const nsTArray<const MediaTrackConstraintSet*>& aConstraintSets)
|
||||
size_t
|
||||
MediaEngineWebRTCVideoSource::NumCapabilities()
|
||||
{
|
||||
NS_ConvertUTF16toUTF8 uniqueId(mUniqueId);
|
||||
NS_ConvertUTF16toUTF8 uniqueId(mUniqueId); // TODO: optimize this?
|
||||
|
||||
int num = mViECapture->NumberOfCapabilities(uniqueId.get(), kMaxUniqueIdLength);
|
||||
if (num <= 0) {
|
||||
return true;
|
||||
if (num > 0) {
|
||||
return num;
|
||||
}
|
||||
// Mac doesn't support capabilities.
|
||||
//
|
||||
// Hardcode generic desktop capabilities modeled on OSX camera.
|
||||
// Note: Values are empirically picked to be OSX friendly, as on OSX, values
|
||||
// other than these cause the source to not produce.
|
||||
|
||||
CapabilitySet candidateSet;
|
||||
for (int i = 0; i < num; i++) {
|
||||
candidateSet.AppendElement(i);
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < aConstraintSets.Length(); j++) {
|
||||
for (size_t i = 0; i < candidateSet.Length(); ) {
|
||||
webrtc::CaptureCapability cap;
|
||||
mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength,
|
||||
candidateSet[i], cap);
|
||||
if (!SatisfiesConstraintSet(*aConstraintSets[j], cap)) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
if (mHardcodedCapabilities.IsEmpty()) {
|
||||
for (int i = 0; i < 9; i++) {
|
||||
webrtc::CaptureCapability c;
|
||||
c.width = 1920 - i*128;
|
||||
c.height = 1080 - i*72;
|
||||
c.maxFPS = 30;
|
||||
mHardcodedCapabilities.AppendElement(c);
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
webrtc::CaptureCapability c;
|
||||
c.width = 640 - i*40;
|
||||
c.height = 480 - i*30;
|
||||
c.maxFPS = 30;
|
||||
mHardcodedCapabilities.AppendElement(c);
|
||||
}
|
||||
}
|
||||
return !!candidateSet.Length();
|
||||
return mHardcodedCapabilities.Length();
|
||||
}
|
||||
|
||||
void
|
||||
MediaEngineWebRTCVideoSource::ChooseCapability(
|
||||
const VideoTrackConstraintsN &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
MediaEngineWebRTCVideoSource::GetCapability(size_t aIndex,
|
||||
webrtc::CaptureCapability& aOut)
|
||||
{
|
||||
NS_ConvertUTF16toUTF8 uniqueId(mUniqueId);
|
||||
int num = mViECapture->NumberOfCapabilities(uniqueId.get(), kMaxUniqueIdLength);
|
||||
if (num <= 0) {
|
||||
// Mac doesn't support capabilities.
|
||||
return GuessCapability(aConstraints, aPrefs);
|
||||
if (!mHardcodedCapabilities.IsEmpty()) {
|
||||
MediaEngineCameraVideoSource::GetCapability(aIndex, aOut);
|
||||
}
|
||||
|
||||
// The rest is the full algorithm for cameras that can list their capabilities.
|
||||
|
||||
LOG(("ChooseCapability: prefs: %dx%d @%d-%dfps",
|
||||
aPrefs.mWidth, aPrefs.mHeight, aPrefs.mFPS, aPrefs.mMinFPS));
|
||||
|
||||
CapabilitySet candidateSet;
|
||||
for (int i = 0; i < num; i++) {
|
||||
candidateSet.AppendElement(i);
|
||||
}
|
||||
|
||||
// Pick among capabilities: First apply required constraints.
|
||||
|
||||
for (uint32_t i = 0; i < candidateSet.Length();) {
|
||||
webrtc::CaptureCapability cap;
|
||||
mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength,
|
||||
candidateSet[i], cap);
|
||||
if (!SatisfiesConstraintSet(aConstraints.mRequired, cap)) {
|
||||
candidateSet.RemoveElementAt(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
CapabilitySet tailSet;
|
||||
|
||||
// Then apply advanced (formerly known as optional) constraints.
|
||||
|
||||
if (aConstraints.mAdvanced.WasPassed()) {
|
||||
auto &array = aConstraints.mAdvanced.Value();
|
||||
|
||||
for (uint32_t i = 0; i < array.Length(); i++) {
|
||||
CapabilitySet rejects;
|
||||
for (uint32_t j = 0; j < candidateSet.Length();) {
|
||||
webrtc::CaptureCapability cap;
|
||||
mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength,
|
||||
candidateSet[j], cap);
|
||||
if (!SatisfiesConstraintSet(array[i], cap)) {
|
||||
rejects.AppendElement(candidateSet[j]);
|
||||
candidateSet.RemoveElementAt(j);
|
||||
} else {
|
||||
++j;
|
||||
}
|
||||
}
|
||||
(candidateSet.Length()? tailSet : candidateSet).MoveElementsFrom(rejects);
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidateSet.Length()) {
|
||||
candidateSet.AppendElement(0);
|
||||
}
|
||||
|
||||
int prefWidth = aPrefs.GetWidth();
|
||||
int prefHeight = aPrefs.GetHeight();
|
||||
|
||||
// Default is closest to available capability but equal to or below;
|
||||
// otherwise closest above. Since we handle the num=0 case above and
|
||||
// take the first entry always, we can never exit uninitialized.
|
||||
|
||||
webrtc::CaptureCapability cap;
|
||||
bool higher = true;
|
||||
for (uint32_t i = 0; i < candidateSet.Length(); i++) {
|
||||
mViECapture->GetCaptureCapability(NS_ConvertUTF16toUTF8(mUniqueId).get(),
|
||||
kMaxUniqueIdLength, candidateSet[i], cap);
|
||||
if (higher) {
|
||||
if (i == 0 ||
|
||||
(mCapability.width > cap.width && mCapability.height > cap.height)) {
|
||||
// closer than the current choice
|
||||
mCapability = cap;
|
||||
// FIXME: expose expected capture delay?
|
||||
}
|
||||
if (cap.width <= (uint32_t) prefWidth && cap.height <= (uint32_t) prefHeight) {
|
||||
higher = false;
|
||||
}
|
||||
} else {
|
||||
if (cap.width > (uint32_t) prefWidth || cap.height > (uint32_t) prefHeight ||
|
||||
cap.maxFPS < (uint32_t) aPrefs.mMinFPS) {
|
||||
continue;
|
||||
}
|
||||
if (mCapability.width < cap.width && mCapability.height < cap.height) {
|
||||
mCapability = cap;
|
||||
// FIXME: expose expected capture delay?
|
||||
}
|
||||
}
|
||||
// Same resolution, maybe better format or FPS match
|
||||
if (mCapability.width == cap.width && mCapability.height == cap.height) {
|
||||
// FPS too low
|
||||
if (cap.maxFPS < (uint32_t) aPrefs.mMinFPS) {
|
||||
continue;
|
||||
}
|
||||
// Better match
|
||||
if (cap.maxFPS < mCapability.maxFPS) {
|
||||
mCapability = cap;
|
||||
} else if (cap.maxFPS == mCapability.maxFPS) {
|
||||
// Resolution and FPS the same, check format
|
||||
if (cap.rawType == webrtc::RawVideoType::kVideoI420
|
||||
|| cap.rawType == webrtc::RawVideoType::kVideoYUY2
|
||||
|| cap.rawType == webrtc::RawVideoType::kVideoYV12) {
|
||||
mCapability = cap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
LOG(("chose cap %dx%d @%dfps codec %d raw %d",
|
||||
mCapability.width, mCapability.height, mCapability.maxFPS,
|
||||
mCapability.codecType, mCapability.rawType));
|
||||
NS_ConvertUTF16toUTF8 uniqueId(mUniqueId); // TODO: optimize this?
|
||||
mViECapture->GetCaptureCapability(uniqueId.get(), kMaxUniqueIdLength, aIndex, aOut);
|
||||
}
|
||||
|
||||
nsresult
|
||||
MediaEngineWebRTCVideoSource::Allocate(const VideoTrackConstraintsN &aConstraints,
|
||||
MediaEngineWebRTCVideoSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
|
||||
const MediaEnginePrefs &aPrefs)
|
||||
{
|
||||
LOG((__FUNCTION__));
|
||||
|
@ -330,8 +207,9 @@ MediaEngineWebRTCVideoSource::Allocate(const VideoTrackConstraintsN &aConstraint
|
|||
// Note: if shared, we don't allow a later opener to affect the resolution.
|
||||
// (This may change depending on spec changes for Constraints/settings)
|
||||
|
||||
ChooseCapability(aConstraints, aPrefs);
|
||||
|
||||
if (!ChooseCapability(aConstraints, aPrefs)) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
if (mViECapture->AllocateCaptureDevice(NS_ConvertUTF16toUTF8(mUniqueId).get(),
|
||||
kMaxUniqueIdLength, mCaptureIndex)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "MediaTrackConstraints.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
template<class ValueType>
|
||||
template<class ConstrainRange>
|
||||
void
|
||||
NormalizedConstraintSet::Range<ValueType>::SetFrom(const ConstrainRange& aOther)
|
||||
{
|
||||
if (aOther.mIdeal.WasPassed()) {
|
||||
mIdeal.Construct(aOther.mIdeal.Value());
|
||||
}
|
||||
if (aOther.mExact.WasPassed()) {
|
||||
mMin = aOther.mExact.Value();
|
||||
mMax = aOther.mExact.Value();
|
||||
} else {
|
||||
if (aOther.mMin.WasPassed()) {
|
||||
mMin = aOther.mMin.Value();
|
||||
}
|
||||
if (aOther.mMax.WasPassed()) {
|
||||
mMax = aOther.mMax.Value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedConstraintSet::LongRange::LongRange(
|
||||
const dom::OwningLongOrConstrainLongRange& aOther, bool advanced)
|
||||
: Range<int32_t>(1 + INT32_MIN, INT32_MAX) // +1 avoids Windows compiler bug
|
||||
{
|
||||
if (aOther.IsLong()) {
|
||||
if (advanced) {
|
||||
mMin = mMax = aOther.GetAsLong();
|
||||
} else {
|
||||
mIdeal.Construct(aOther.GetAsLong());
|
||||
}
|
||||
} else {
|
||||
SetFrom(aOther.GetAsConstrainLongRange());
|
||||
}
|
||||
}
|
||||
|
||||
NormalizedConstraintSet::DoubleRange::DoubleRange(
|
||||
const dom::OwningDoubleOrConstrainDoubleRange& aOther, bool advanced)
|
||||
: Range<double>(-std::numeric_limits<double>::infinity(),
|
||||
std::numeric_limits<double>::infinity())
|
||||
{
|
||||
if (aOther.IsDouble()) {
|
||||
if (advanced) {
|
||||
mMin = mMax = aOther.GetAsDouble();
|
||||
} else {
|
||||
mIdeal.Construct(aOther.GetAsDouble());
|
||||
}
|
||||
} else {
|
||||
SetFrom(aOther.GetAsConstrainDoubleRange());
|
||||
}
|
||||
}
|
||||
|
||||
FlattenedConstraints::FlattenedConstraints(const dom::MediaTrackConstraints& aOther)
|
||||
: NormalizedConstraintSet(aOther, false)
|
||||
{
|
||||
if (aOther.mAdvanced.WasPassed()) {
|
||||
const auto& advanced = aOther.mAdvanced.Value();
|
||||
for (size_t i = 0; i < advanced.Length(); i++) {
|
||||
NormalizedConstraintSet set(advanced[i], true);
|
||||
// Must only apply compatible i.e. inherently non-overconstraining sets
|
||||
// This rule is pretty much why this code is centralized here.
|
||||
if (mWidth.Intersects(set.mWidth) &&
|
||||
mHeight.Intersects(set.mHeight) &&
|
||||
mFrameRate.Intersects(set.mFrameRate)) {
|
||||
mWidth.Intersect(set.mWidth);
|
||||
mHeight.Intersect(set.mHeight);
|
||||
mFrameRate.Intersect(set.mFrameRate);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/dom/MediaStreamTrackBinding.h"
|
||||
#include "mozilla/dom/MediaTrackConstraintSetBinding.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
|
@ -18,8 +19,8 @@ static const char* EnumToASCII(const EnumValuesStrings& aStrings, Enum aValue) {
|
|||
}
|
||||
|
||||
template<class EnumValuesStrings, class Enum>
|
||||
static Enum StringToEnum(const EnumValuesStrings& aStrings, const nsAString& aValue,
|
||||
Enum aDefaultValue) {
|
||||
static Enum StringToEnum(const EnumValuesStrings& aStrings,
|
||||
const nsAString& aValue, Enum aDefaultValue) {
|
||||
for (size_t i = 0; aStrings[i].value; i++) {
|
||||
if (aValue.EqualsASCII(aStrings[i].value)) {
|
||||
return Enum(i);
|
||||
|
@ -28,101 +29,57 @@ static Enum StringToEnum(const EnumValuesStrings& aStrings, const nsAString& aVa
|
|||
return aDefaultValue;
|
||||
}
|
||||
|
||||
// Normalized internal version of MediaTrackConstraints to simplify downstream
|
||||
// processing. This implementation-only helper is included as needed by both
|
||||
// MediaManager (for gUM camera selection) and MediaEngine (for applyConstraints).
|
||||
// Helper classes for orthogonal constraints without interdependencies.
|
||||
// Instead of constraining values, constrain the constraints themselves.
|
||||
|
||||
template<typename T>
|
||||
class MediaTrackConstraintsN : public dom::MediaTrackConstraints
|
||||
struct NormalizedConstraintSet
|
||||
{
|
||||
public:
|
||||
typedef T Kind;
|
||||
dom::Sequence<Kind> mRequireN;
|
||||
bool mUnsupportedRequirement;
|
||||
MediaTrackConstraintSet mRequired;
|
||||
dom::Sequence<MediaTrackConstraintSet> mNonrequired;
|
||||
dom::MediaSourceEnum mMediaSourceEnumValue;
|
||||
|
||||
MediaTrackConstraintsN(const dom::MediaTrackConstraints &aOther,
|
||||
const dom::EnumEntry* aStrings)
|
||||
: dom::MediaTrackConstraints(aOther)
|
||||
, mUnsupportedRequirement(false)
|
||||
, mStrings(aStrings)
|
||||
template<class ValueType>
|
||||
struct Range
|
||||
{
|
||||
if (mRequire.WasPassed()) {
|
||||
auto& array = mRequire.Value();
|
||||
for (size_t i = 0; i < array.Length(); i++) {
|
||||
auto value = StringToEnum(mStrings, array[i], Kind::Other);
|
||||
if (value != Kind::Other) {
|
||||
mRequireN.AppendElement(value);
|
||||
} else {
|
||||
mUnsupportedRequirement = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// treat MediaSource special because it's always required
|
||||
mRequired.mMediaSource = mMediaSource;
|
||||
ValueType mMin, mMax;
|
||||
dom::Optional<ValueType> mIdeal;
|
||||
|
||||
mMediaSourceEnumValue = StringToEnum(dom::MediaSourceEnumValues::strings,
|
||||
mMediaSource,
|
||||
dom::MediaSourceEnum::Other);
|
||||
if (mAdvanced.WasPassed()) {
|
||||
if(mMediaSourceEnumValue != dom::MediaSourceEnum::Camera) {
|
||||
// iterate through advanced, forcing mediaSource to match "root"
|
||||
auto& array = mAdvanced.Value();
|
||||
for (uint32_t i = 0; i < array.Length(); i++) {
|
||||
auto& ms = array[i].mMediaSource;
|
||||
if (ms.EqualsASCII(EnumToASCII(dom::MediaSourceEnumValues::strings,
|
||||
dom::MediaSourceEnum::Camera))) {
|
||||
ms = mMediaSource;
|
||||
}
|
||||
}
|
||||
}
|
||||
Range(ValueType aMin, ValueType aMax) : mMin(aMin), mMax(aMax) {}
|
||||
|
||||
template<class ConstrainRange>
|
||||
void SetFrom(const ConstrainRange& aOther);
|
||||
ValueType Clamp(ValueType n) const { return std::max(mMin, std::min(n, mMax)); }
|
||||
bool Intersects(const Range& aOther) const {
|
||||
return mMax >= aOther.mMin && mMin <= aOther.mMax;
|
||||
}
|
||||
}
|
||||
protected:
|
||||
MediaTrackConstraintSet& Triage(const Kind kind) {
|
||||
if (mRequireN.IndexOf(kind) != mRequireN.NoIndex) {
|
||||
return mRequired;
|
||||
} else {
|
||||
mNonrequired.AppendElement(MediaTrackConstraintSet());
|
||||
return mNonrequired[mNonrequired.Length()-1];
|
||||
void Intersect(const Range& aOther) {
|
||||
MOZ_ASSERT(Intersects(aOther));
|
||||
mMin = std::max(mMin, aOther.mMin);
|
||||
mMax = std::min(mMax, aOther.mMax);
|
||||
}
|
||||
}
|
||||
private:
|
||||
const dom::EnumEntry* mStrings;
|
||||
};
|
||||
|
||||
struct LongRange : public Range<int32_t>
|
||||
{
|
||||
LongRange(const dom::OwningLongOrConstrainLongRange& aOther, bool advanced);
|
||||
};
|
||||
|
||||
struct DoubleRange : public Range<double>
|
||||
{
|
||||
DoubleRange(const dom::OwningDoubleOrConstrainDoubleRange& aOther,
|
||||
bool advanced);
|
||||
};
|
||||
|
||||
// Do you need to add your constraint here? Only if your code uses flattening
|
||||
LongRange mWidth, mHeight;
|
||||
DoubleRange mFrameRate;
|
||||
|
||||
NormalizedConstraintSet(const dom::MediaTrackConstraintSet& aOther,
|
||||
bool advanced)
|
||||
: mWidth(aOther.mWidth, advanced)
|
||||
, mHeight(aOther.mHeight, advanced)
|
||||
, mFrameRate(aOther.mFrameRate, advanced) {}
|
||||
};
|
||||
|
||||
struct AudioTrackConstraintsN :
|
||||
public MediaTrackConstraintsN<dom::SupportedAudioConstraints>
|
||||
struct FlattenedConstraints : public NormalizedConstraintSet
|
||||
{
|
||||
MOZ_IMPLICIT AudioTrackConstraintsN(const dom::MediaTrackConstraints &aOther)
|
||||
: MediaTrackConstraintsN<dom::SupportedAudioConstraints>(aOther, // B2G ICS compiler bug
|
||||
dom::SupportedAudioConstraintsValues::strings) {}
|
||||
};
|
||||
|
||||
struct VideoTrackConstraintsN :
|
||||
public MediaTrackConstraintsN<dom::SupportedVideoConstraints>
|
||||
{
|
||||
MOZ_IMPLICIT VideoTrackConstraintsN(const dom::MediaTrackConstraints &aOther)
|
||||
: MediaTrackConstraintsN<dom::SupportedVideoConstraints>(aOther,
|
||||
dom::SupportedVideoConstraintsValues::strings) {
|
||||
if (mFacingMode.WasPassed()) {
|
||||
Triage(Kind::FacingMode).mFacingMode.Construct(mFacingMode.Value());
|
||||
}
|
||||
// Reminder: add handling for new constraints both here & SatisfyConstraintSet
|
||||
Triage(Kind::Width).mWidth = mWidth;
|
||||
Triage(Kind::Height).mHeight = mHeight;
|
||||
Triage(Kind::FrameRate).mFrameRate = mFrameRate;
|
||||
if (mBrowserWindow.WasPassed()) {
|
||||
Triage(Kind::BrowserWindow).mBrowserWindow.Construct(mBrowserWindow.Value());
|
||||
}
|
||||
if (mScrollWithPage.WasPassed()) {
|
||||
Triage(Kind::ScrollWithPage).mScrollWithPage.Construct(mScrollWithPage.Value());
|
||||
}
|
||||
// treat MediaSource special because it's always required
|
||||
mRequired.mMediaSource = mMediaSource;
|
||||
}
|
||||
explicit FlattenedConstraints(const dom::MediaTrackConstraints& aOther);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ if CONFIG['MOZ_WEBRTC']:
|
|||
'MediaEngineTabVideoSource.cpp',
|
||||
'MediaEngineWebRTCAudio.cpp',
|
||||
'MediaEngineWebRTCVideo.cpp',
|
||||
'MediaTrackConstraints.cpp',
|
||||
]
|
||||
# MediaEngineWebRTC.cpp needs to be built separately.
|
||||
SOURCES += [
|
||||
|
|
|
@ -1837,19 +1837,30 @@ TranslateToNPCocoaEvent(WidgetGUIEvent* anEvent, nsIFrame* aObjectFrame)
|
|||
{
|
||||
WidgetKeyboardEvent* keyEvent = anEvent->AsKeyboardEvent();
|
||||
|
||||
cocoaEvent.data.key.keyCode = keyEvent->mNativeKeyCode;
|
||||
cocoaEvent.data.key.isARepeat = keyEvent->mIsRepeat;
|
||||
cocoaEvent.data.key.modifierFlags = keyEvent->mNativeModifierFlags;
|
||||
const char16_t* nativeChars = keyEvent->mNativeCharacters.get();
|
||||
cocoaEvent.data.key.characters =
|
||||
(NPNSString*)::CFStringCreateWithCharacters(NULL,
|
||||
reinterpret_cast<const UniChar*>(nativeChars),
|
||||
keyEvent->mNativeCharacters.Length());
|
||||
const char16_t* nativeCharsIgnoringModifiers = keyEvent->mNativeCharactersIgnoringModifiers.get();
|
||||
cocoaEvent.data.key.charactersIgnoringModifiers =
|
||||
(NPNSString*)::CFStringCreateWithCharacters(NULL,
|
||||
reinterpret_cast<const UniChar*>(nativeCharsIgnoringModifiers),
|
||||
keyEvent->mNativeCharactersIgnoringModifiers.Length());
|
||||
// That keyEvent->mPluginTextEventString is non-empty is a signal that we should
|
||||
// create a text event for the plugin, instead of a key event.
|
||||
if ((anEvent->message == NS_KEY_DOWN) && !keyEvent->mPluginTextEventString.IsEmpty()) {
|
||||
cocoaEvent.type = NPCocoaEventTextInput;
|
||||
const char16_t* pluginTextEventString = keyEvent->mPluginTextEventString.get();
|
||||
cocoaEvent.data.text.text = (NPNSString*)
|
||||
::CFStringCreateWithCharacters(NULL,
|
||||
reinterpret_cast<const UniChar*>(pluginTextEventString),
|
||||
keyEvent->mPluginTextEventString.Length());
|
||||
} else {
|
||||
cocoaEvent.data.key.keyCode = keyEvent->mNativeKeyCode;
|
||||
cocoaEvent.data.key.isARepeat = keyEvent->mIsRepeat;
|
||||
cocoaEvent.data.key.modifierFlags = keyEvent->mNativeModifierFlags;
|
||||
const char16_t* nativeChars = keyEvent->mNativeCharacters.get();
|
||||
cocoaEvent.data.key.characters = (NPNSString*)
|
||||
::CFStringCreateWithCharacters(NULL,
|
||||
reinterpret_cast<const UniChar*>(nativeChars),
|
||||
keyEvent->mNativeCharacters.Length());
|
||||
const char16_t* nativeCharsIgnoringModifiers = keyEvent->mNativeCharactersIgnoringModifiers.get();
|
||||
cocoaEvent.data.key.charactersIgnoringModifiers = (NPNSString*)
|
||||
::CFStringCreateWithCharacters(NULL,
|
||||
reinterpret_cast<const UniChar*>(nativeCharsIgnoringModifiers),
|
||||
keyEvent->mNativeCharactersIgnoringModifiers.Length());
|
||||
}
|
||||
break;
|
||||
}
|
||||
case NS_FOCUS_CONTENT:
|
||||
|
@ -1921,56 +1932,37 @@ nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
|
|||
return nsEventStatus_eIgnore;
|
||||
}
|
||||
|
||||
if (cocoaEvent.type == NPCocoaEventKeyDown) {
|
||||
ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
|
||||
if (ctiPanel && ctiPanel->IsInComposition()) {
|
||||
nsAutoString outText;
|
||||
ctiPanel->InterpretKeyEvent(&cocoaEvent, outText);
|
||||
if (!outText.IsEmpty()) {
|
||||
CFStringRef cfString = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
|
||||
reinterpret_cast<const UniChar*>(outText.get()),
|
||||
outText.Length());
|
||||
|
||||
NPCocoaEvent textEvent;
|
||||
InitializeNPCocoaEvent(&textEvent);
|
||||
textEvent.type = NPCocoaEventTextInput;
|
||||
textEvent.data.text.text = (NPNSString*)cfString;
|
||||
|
||||
mInstance->HandleEvent(&textEvent, nullptr);
|
||||
}
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
if (cocoaEvent.type == NPCocoaEventTextInput) {
|
||||
mInstance->HandleEvent(&cocoaEvent, nullptr);
|
||||
return nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
|
||||
int16_t response = kNPEventNotHandled;
|
||||
mInstance->HandleEvent(&cocoaEvent,
|
||||
&response,
|
||||
NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
|
||||
if (response == kNPEventStartIME) {
|
||||
nsAutoString outText;
|
||||
ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
|
||||
|
||||
// Place ctiPanel by passing the coordinates of the bottom-left of the plugin,
|
||||
// in screen-coordinates.
|
||||
double screenX, screenY;
|
||||
ConvertPoint(0.0, mPluginFrame->GetScreenRect().height, NPCoordinateSpacePlugin,
|
||||
&screenX, &screenY, NPCoordinateSpaceScreen);
|
||||
|
||||
ctiPanel->PlacePanel(screenX, screenY);
|
||||
ctiPanel->InterpretKeyEvent(&cocoaEvent, outText);
|
||||
|
||||
if (!outText.IsEmpty()) {
|
||||
CFStringRef cfString = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
|
||||
reinterpret_cast<const UniChar*>(outText.get()),
|
||||
outText.Length());
|
||||
|
||||
NPCocoaEvent textEvent;
|
||||
InitializeNPCocoaEvent(&textEvent);
|
||||
textEvent.type = NPCocoaEventTextInput;
|
||||
textEvent.data.text.text = (NPNSString*)cfString;
|
||||
|
||||
mInstance->HandleEvent(&textEvent, nullptr);
|
||||
}
|
||||
if ((response == kNPEventStartIME) && (cocoaEvent.type == NPCocoaEventKeyDown)) {
|
||||
nsIWidget* widget = mPluginFrame->GetNearestWidget();
|
||||
if (widget) {
|
||||
const WidgetKeyboardEvent* keyEvent = anEvent.AsKeyboardEvent();
|
||||
double screenX, screenY;
|
||||
ConvertPoint(0.0, mPluginFrame->GetScreenRect().height,
|
||||
NPCoordinateSpacePlugin, &screenX, &screenY,
|
||||
NPCoordinateSpaceScreen);
|
||||
nsAutoString outText;
|
||||
if (NS_SUCCEEDED(widget->StartPluginIME(*keyEvent, screenX, screenY, outText)) &&
|
||||
!outText.IsEmpty()) {
|
||||
CFStringRef cfString =
|
||||
::CFStringCreateWithCharacters(kCFAllocatorDefault,
|
||||
reinterpret_cast<const UniChar*>(outText.get()),
|
||||
outText.Length());
|
||||
NPCocoaEvent textEvent;
|
||||
InitializeNPCocoaEvent(&textEvent);
|
||||
textEvent.type = NPCocoaEventTextInput;
|
||||
textEvent.data.text.text = (NPNSString*)cfString;
|
||||
mInstance->HandleEvent(&textEvent, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool handled = (response == kNPEventHandled || response == kNPEventStartIME);
|
||||
|
|
|
@ -52,6 +52,8 @@ const kUDP_WAKEUP_WS_STATUS_CODE = 4774; // WebSocket Close status code sent
|
|||
const kCHILD_PROCESS_MESSAGES = ["Push:Register", "Push:Unregister",
|
||||
"Push:Registrations"];
|
||||
|
||||
const kWS_MAX_WENTDOWN = 2;
|
||||
|
||||
// This is a singleton
|
||||
this.PushDB = function PushDB() {
|
||||
debug("PushDB()");
|
||||
|
@ -65,7 +67,7 @@ this.PushDB.prototype = {
|
|||
__proto__: IndexedDBHelper.prototype,
|
||||
|
||||
upgradeSchema: function(aTransaction, aDb, aOldVersion, aNewVersion) {
|
||||
debug("PushDB.upgradeSchema()")
|
||||
debug("PushDB.upgradeSchema()");
|
||||
|
||||
let objectStore = aDb.createObjectStore(kPUSHDB_STORE_NAME,
|
||||
{ keyPath: "channelID" });
|
||||
|
@ -481,6 +483,12 @@ this.PushService = {
|
|||
*/
|
||||
_upperLimit: 0,
|
||||
|
||||
/**
|
||||
* Count the times WebSocket goes down without receiving Pings
|
||||
* so we can re-enable the ping recalculation algorithm
|
||||
*/
|
||||
_wsWentDownCounter: 0,
|
||||
|
||||
/**
|
||||
* Sends a message to the Push Server through an open websocket.
|
||||
* typeof(msg) shall be an object
|
||||
|
@ -678,6 +686,11 @@ this.PushService = {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!wsWentDown) {
|
||||
debug('Setting websocket down counter to 0');
|
||||
this._wsWentDownCounter = 0;
|
||||
}
|
||||
|
||||
if (!this._recalculatePing && !wsWentDown) {
|
||||
debug('We do not need to recalculate the ping now, based on previous data');
|
||||
return;
|
||||
|
@ -722,6 +735,24 @@ this.PushService = {
|
|||
|
||||
let nextPingInterval;
|
||||
let lastTriedPingInterval = prefs.get('pingInterval');
|
||||
|
||||
if (!this._recalculatePing && wsWentDown) {
|
||||
debug('Websocket disconnected without ping adaptative algorithm running');
|
||||
this._wsWentDownCounter++;
|
||||
if (this._wsWentDownCounter > kWS_MAX_WENTDOWN) {
|
||||
debug('Too many disconnects. Reenabling ping adaptative algoritm');
|
||||
this._wsWentDownCounter = 0;
|
||||
this._recalculatePing = true;
|
||||
this._lastGoodPingInterval = Math.floor(lastTriedPingInterval / 2);
|
||||
nextPingInterval = this._lastGoodPingInterval;
|
||||
prefs.set('pingInterval', nextPingInterval);
|
||||
this._save(ns, nextPingInterval);
|
||||
return;
|
||||
}
|
||||
|
||||
debug('We do not need to recalculate the ping, based on previous data');
|
||||
}
|
||||
|
||||
if (wsWentDown) {
|
||||
debug('The WebSocket was disconnected, calculating next ping');
|
||||
|
||||
|
@ -770,6 +801,10 @@ this.PushService = {
|
|||
debug('Setting the pingInterval to ' + nextPingInterval);
|
||||
prefs.set('pingInterval', nextPingInterval);
|
||||
|
||||
this._save(ns, nextPingInterval);
|
||||
},
|
||||
|
||||
_save: function(ns, nextPingInterval){
|
||||
//Save values for our current network
|
||||
if (ns.ip) {
|
||||
prefs.set('pingInterval.mobile', nextPingInterval);
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
*/
|
||||
|
||||
[Exposed=ServiceWorker]
|
||||
interface ServiceWorkerClient {
|
||||
interface Client {
|
||||
readonly attribute unsigned long id;
|
||||
|
||||
[Throws]
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче