mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 05:37:11 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1254888 - Part 1: Add logging macro to dom/presentation. r=schien (aeecfd2c12) - Bug 1254888 - Part 2: Add log to PresentationSessionInfo and Transport. r=schien (8d705bfda0) - Bug 1148307 - Part1, separate object bruilder from nsIPresentationSessionTransport, r=smaug (038cc48549) - Bug 1239242 - support PresentationRequest.startWithDevice(). r=smaug. (8bb527a997) - Bug 1148307 - Part 2, let session transport send DOM string. r=smaug (61ac0e8d64) - Bug 1148307 - Part 3, implement session transport with DataChannel. r=jib. (c4d124c093) - Bug 1226144 - Free sessionId after using it. r=selin (ee0d36f996) - Bug 1148307 - Part 4, use data channel in substitution for TCP session transport (in-process), r=smaug (8954ab54f8) - Bug 1148307 - Part 5, pref off data channel session transport, r=smaug (61c0c17d1f) - some pref. cleanup (21e17660e7) - add some font names and aliases (cb38962246) - remove unused dom.max_child_script_run_time (d214b353d4) - align strange layout.css.scroll-snap.enabled overwrite (f2562a5cc1) - reshuffle some preferences, remove unused (41f586186b) - more reshuffle and cleanup of preferences (0208aa32a3) - Bug 1168891 Part 1 - Refine two functions related to caret positioning. r=mats (86d718d60e) - Bug 1168891 Part 2 - Allow one caret to be dragged across the other caret. r=mats (9276eb7728) - part of Bug 1252802 - Web page scrolls when dragging caret in editable, r=snorp (31dade8b77) - Bug 1235508 - Re-implement fast Phone number selection on long-press, r=TYLin (59b6371d17) - Bug 1249201 Part 1 - Add "scroll" reason to CaretStateChangedEvent. r=smaug (b92ff6cbfc) - Bug 1249201 Part 2 - Show carets continuously when panning or zooming. r=mats,sebastian (ca5c51c479) - Bug 1245246: Add null check for mDocViewerPrint in nsPrintEngine::FirePrintingErrorEvent. r=roc (e9d5b49a3f) - Bug 1025267 - Make some -moz- prefixed pseudo-classes chrome-only. r=bz (238f7a85d4) - Bug 1259889 Part 1 - Add @supports -moz-bool-pref for internal-only style sheets. r=heycam (d716a7b884) - Bug 1237633 - Part 1: Percentages are not allowed in a <source-size-value>. r=jdm (52ccffbf86) - Bug 1081362 - Change nsStyleBasicShape pointer to an nsRefPtr, to avoid leak in unexpected case. r=dholbert (2a5cb8ffdd) - Bug 1264317 - Make the basic shape clip-path clipping use nsCSSValue::Array instead of nsCSSValueList. r=dholbert (7aaf39f2d7) - Bug 1247150 - Consistently use StyleSheetHandle::RefPtr* for outparams in nsLayoutStylesheetCache. r=dholbert (ddc85f29f8) - Bug 1251848: Check StyleSheetHandles for being null-flavored before derefing them, in assertions within nsLayoutStylesheetCache::InvalidateSheet. r=bholley (edb3924075) - Bug 1245260 - Add crashtest; r=hiro (6347e37750) - Bug 460209 - Add crashtest. (97b4786de2) - Bug 474377 - Add crashtest. (516b4e8164) - Bug 1264396 - Don't allow animation of 'display' property; r=heycam (6e94bcb26a) - missing bit of 759568 - Part 1 (fc954f075b) - part of Bug 1037483 replace microdata with microformats (4ff01e11d6) - Bug 1245334 - Make PromiseMessage.jsm ids more meaningful. r=baku (913ac1b9a5) - Bug 1094201 - Implement an Integration.jsm module for low-overhead registration of overrides. r=mak (9982624b90) - Bug 1167663 - Mark nsCSSKeyframeStyleDeclaration/nsCSSPageStyleDeclaration::mRule as MOZ_NON_OWNING_REF. r=dbaron (6d4e9751a1) - Bug 1244992 - Avoid double-counting in various refcounted types related to nsCSSValue. r=heycam. (c830949dd9) - Bug 1262646 - Change the outparams passed to nsStyleUtil::AppendEscapedCSSString from nsString to nsAutoString. r=dholbert (2b0caadf9d) - Bug 1247336 - De-dupe changes in ActiveLayerTracker before treating property as animated. r=roc (c44ed5aee6) - space fix (5e79d245ea) - Bug 1266288 - Track changes to all margin properties for scroll-linked effects. r=mstange (fed6994e4d) - Bug 1259641 - Do not force reflow for all tabs when size mode changed. r=smaug (70847cc6d2) - Bug 1261265 - Fix nsStyleContext::MoveTo flag assertions to allow mismatch on parents if bit is set on child. r=dholbert (3e6b08372e) - Bug 1264837 Part 43 - Remove SVGFEUnstyledLeafFrameBase. r=dholbert (bb55feda77) - Remove mention of old SVG text pref in comment; no bug. (DONTBUILD) (3a618aca18) - Bug 752638, part 1 - Move SVGTextFrame::SetupContextPaint to nsSVGUtils. r=heycam (c125c2903f) - Bug 1258843 - Don't build SVG display items if their visibility is hidden. r=dholbert (150c3b0059) - Bug 1258650. Properly use aExtraMasksTransform when combining masks. r=Bas,a=kwierso (ba5ea1928b) - Bug 1263789 - Stop nsSVGMaskFrameNEON.h from polluting the global namespace. r=dholbert (e2c8544d35) - Bug 1162418 - Try to find a suitable non-zero dimension to use when containing block's inline-size depends on an SVG element which is specified as a percentage of its container. r=jwatt (3eab79c8a4) - Bug 1250143. Account for border/padding on outer <svg> elements in GeometryUtils. r=mats (f307820b75) - Bug 1243623. Don't skip unregistering a table part if we have a split table. r=mats (35bb0821c1) - Bug 1203417. Propagate error result from PaintTableFrame. r=seth (866e47b3e4) - Bug 1209780. Propagate the use of MOZ_MUST_USE DrawResult in nsTablePainter. r=seth (851618d06c) - var-const (29d5e9f859) - Bug 1209780. Propagate the use of MOZ_MUST_USE DrawResult in nsTreeBodyFrame::PaintText. r=seth (1ce563ea18) - Bug 1203626 - remove the unused argument from nsTreeBodyFrame::GetTwistyRect. r=mattwoodrow (03293f52b5) - Bug 1218041, part 1: Give nsTreeBodyFrame::PaintImage a fallback codepath for painting SVG images with no explicit height or width. r=seth (b6fd3a39f7) - Bug 1218041, part 2: add reftests for <treecell> SVG-image rendering. (no review) (90231e0bfa) - Bug 1224736: When image size lookup fails in nsTreeBodyFrame::PaintImage, only fall back to use the full destRect if we've got a VectorImage. r=tn (dd7d7667ca) - Bug 1156108 - Make nsTreeColumns::mFirstColumn an nsRefPtr; r=roc (f6888480bc) - Bug 1255069 - use UniquePtr for storage in nsTreeContentView; r=dholbert (598256735f) - Bug 1181560 - ensure previous menus get closed when opening new ones, r=Enn (2c88f3452a) - Bug 1192655 - Make menubar not react to events when it is not visible. r=enn (2bbcbc81a2) - Bug 1197913 - Keep the last hovered item highlighted after moving the cursor outside the <select> drop-down list on Windows. r=neil (abd3240473) - Bug 1228029 - Fix the usage of gtest assertion macros in TestJobScheduler.cpp. r=kats (0fcc9aa6fe) - Bug 1244234 - Simplify joining jobs with the gfx job scheduler. r=jrmuizel (f4b6bbf418) - Bug 1239288 - Add a shutdown test to the gfx job scheduler. r=jrmuizel (fd2432d108) - Bug 1239288 - Fix a race in the win32 job scheduler's shutdown. r=jrmuizel (4e509b4bf3) - Bug 1241161 - make Matrix4x4::ProjectTo2D normalize out perpective where possible. r=mattwoodrow (5a68e396a3) - bits of Bug 1135138 - Remove UNICODE from DEFINES (1eb51a0a79) - Bug 1249640: Part 4 Android to use new blocking. r=snorp (855e5c0dda) - Bug 1234875 - Remove alwaysAcceptSessionCookies pref. r=mak (8bed323449) - Bug 1247912 - convert left side expression to int64_t when assigning to mCookiesLifetimeSec in order to avoid overflow. r=jdm (0cedb68c83) - code and comment style (9215d74a8f) - code and comment style (1d4cda31af) - Bug 1219928 - Skip misspelled words in style blocks. r=enndeakin. (91dd0bcedf) - Bug 1240896 - Use iframe mozbrowser in RDM. r=gl (e77d22985c) - Bug 1240896 - Uplift dimensions to avoid recreating iframe. r=gl (85a3be9131) - Bug 1240896 - Load frame script into RDM browser. r=gl (8f13d807e4) - Bug 1240896 - Port browser_device_width.js to new RDM. r=gl (d91c389a28) - Bug 1240896 - Rebuild existing RDM browser tests to work with remote frames. r=m (546dad6c25) - Bug 1240896 - Improve RDM GCLI test toggling. r=me (0dfb78bc96) - Bug 1251767 - Add WS filter button to net panel; r=honza (8405709965) - Bug 1242988 - Replac styleeditor's _ l10n function with getString. r=pbro (90d264a6e0) - Bug 1241437 - remove workaround from StyleSheetEditor.jsm; r=pbrosset (28223516c3) - Bug 1236968 - autodial telemetry r=mayhemer (3844b9c19e) - Bug 1254310 - Add a hidden pref to temporarily disable Safe Browsing on given hostnames. r=gcp (4955fc88f8) - Bug 772528 - Remove nsFileInputStream::Seek() from nsPartialFileInputStream::Init(). r=baku (15db900fb5) - Bug 1150921 - Add telemetry for response codes to SafeBrowsing requests. r=francois f=bsmedberg (215d50e4ad) - Bug 1164518 - Better logging of completions. r=gcp (95b4fe3731) - Bug 1172688 - Add telemetry for when gethash calls timeout. r=francois, r=bsmedberg (b94a2b38a7) - Bug 1266184 - Implement nsIMIMEInputStream.data getter. r=mcmanus (8c9159c030) - Bug 1239955 - Let DNSService rely on IOService::Offline, r=bagder (336f161d21) - Bug 1260407 - added logging for proxy/pac to aid debugging, r=mcmanus (a179275ca6) - Bug 1259089 - Set TCP socket to non-blocking in sts again, just to be sure. r=mcmanus (bf0656bf07) - Bug 1256473 - Cast values to avoid C4838 on VS2015; r=mayhemer (d4b138dba8) - Bug 1260764 - Creation of PollableEvent needs a lock r=dragana a=kwierso (01c9d5e477) - Bug 652186 - Implement URL Standard's backslash replacement r=mcmanus (6485fa7e8c) - Bug 1042347 - %2e entered in URL bar not normalized leading to denormalized request r=mcmanus (3fc1ff92cd) - Bug 377052 - nsBaseURLParser::ParseURL doesn't handle spaces embedded in the scheme properly r=mcmanus (1f54055b9d) - fix editor format (444d6a62c4) - Bug 1154124 - Prevent recursion when calling HTTP cache entry's callbacks. r=michal (7bdfbf603d) - Bug 1247644 - Don't do any I/O on doomed and unused HTTP cache entries, r=michal (7668d29a36)
This commit is contained in:
+6
-1
@@ -224,7 +224,6 @@ pref("dom.use_watchdog", false);
|
||||
// ensure that those calls don't accidentally trigger the dialog.
|
||||
pref("dom.max_script_run_time", 0);
|
||||
pref("dom.max_chrome_script_run_time", 0);
|
||||
pref("dom.max_child_script_run_time", 0);
|
||||
|
||||
// plugins
|
||||
pref("plugin.disable", true);
|
||||
@@ -1044,6 +1043,12 @@ pref("layout.accessiblecaret.enabled", true);
|
||||
pref("layout.accessiblecaret.use_long_tap_injector", false);
|
||||
#endif
|
||||
|
||||
// The active caret is disallow to be dragged across the other (inactive) caret.
|
||||
pref("layout.accessiblecaret.allow_dragging_across_other_caret", false);
|
||||
|
||||
// Hide carets and text selection dialog during scrolling.
|
||||
pref("layout.accessiblecaret.always_show_when_scrolling", false);
|
||||
|
||||
// Enable sync and mozId with Firefox Accounts.
|
||||
pref("services.sync.fxaccounts.enabled", true);
|
||||
pref("identity.fxaccounts.enabled", true);
|
||||
|
||||
@@ -447,6 +447,8 @@
|
||||
@RESPATH@/components/PresentationDeviceInfoManager.js
|
||||
@RESPATH@/components/BuiltinProviders.manifest
|
||||
@RESPATH@/components/TCPPresentationServer.js
|
||||
@RESPATH@/components/PresentationDataChannelSessionTransport.js
|
||||
@RESPATH@/components/PresentationDataChannelSessionTransport.manifest
|
||||
|
||||
#ifdef MOZ_SECUREELEMENT
|
||||
@RESPATH@/components/ACEService.js
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/* Ensure that hostnames in the whitelisted pref are not blocked. */
|
||||
|
||||
const PREF_WHITELISTED_HOSTNAMES = "urlclassifier.skipHostnames";
|
||||
const TEST_PAGE = "http://www.itisatrap.org/firefox/its-an-attack.html";
|
||||
var tabbrowser = null;
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
tabbrowser = null;
|
||||
Services.prefs.clearUserPref(PREF_WHITELISTED_HOSTNAMES);
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
});
|
||||
|
||||
function testBlockedPage(window) {
|
||||
info("Non-whitelisted pages must be blocked");
|
||||
ok(true, "about:blocked was shown");
|
||||
}
|
||||
|
||||
function testWhitelistedPage(window) {
|
||||
info("Whitelisted pages must be skipped");
|
||||
var getmeout_button = window.document.getElementById("getMeOutButton");
|
||||
var ignorewarning_button = window.document.getElementById("ignoreWarningButton");
|
||||
ok(!getmeout_button, "GetMeOut button not present");
|
||||
ok(!ignorewarning_button, "IgnoreWarning button not present");
|
||||
}
|
||||
|
||||
add_task(function* testNormalBrowsing() {
|
||||
tabbrowser = gBrowser;
|
||||
let tab = tabbrowser.selectedTab = tabbrowser.addTab();
|
||||
|
||||
info("Load a test page that's whitelisted");
|
||||
Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "example.com,www.ItIsaTrap.org,example.net");
|
||||
yield promiseTabLoadEvent(tab, TEST_PAGE, "load");
|
||||
testWhitelistedPage(tab.ownerDocument.defaultView);
|
||||
|
||||
info("Load a test page that's no longer whitelisted");
|
||||
Services.prefs.setCharPref(PREF_WHITELISTED_HOSTNAMES, "");
|
||||
yield promiseTabLoadEvent(tab, TEST_PAGE, "AboutBlockedLoaded");
|
||||
testBlockedPage(tab.ownerDocument.defaultView);
|
||||
});
|
||||
@@ -628,6 +628,8 @@
|
||||
@RESPATH@/components/PresentationDeviceInfoManager.js
|
||||
@RESPATH@/components/BuiltinProviders.manifest
|
||||
@RESPATH@/components/TCPPresentationServer.js
|
||||
@RESPATH@/components/PresentationDataChannelSessionTransport.js
|
||||
@RESPATH@/components/PresentationDataChannelSessionTransport.manifest
|
||||
|
||||
; InputMethod API
|
||||
@RESPATH@/components/MozKeyboard.js
|
||||
|
||||
@@ -120,6 +120,10 @@
|
||||
- in the network details footer for the "Flash" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterFlash "Flash">
|
||||
|
||||
<!-- LOCALIZATION NOTE (netmonitorUI.footer.filterWS): This is the label displayed
|
||||
- in the network details footer for the "WS" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterWS "WS">
|
||||
|
||||
<!-- LOCALIZATION NOTE (netmonitorUI.footer.filterOther): This is the label displayed
|
||||
- in the network details footer for the "Other" filtering button. -->
|
||||
<!ENTITY netmonitorUI.footer.filterOther "Other">
|
||||
|
||||
@@ -983,7 +983,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
*
|
||||
* @param string type
|
||||
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
|
||||
* "flash" or "other".
|
||||
* "flash", "ws" or "other".
|
||||
*/
|
||||
filterOn: function(type = "all") {
|
||||
if (type === "all") {
|
||||
@@ -1026,7 +1026,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
*
|
||||
* @param string type
|
||||
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
|
||||
* "flash" or "other".
|
||||
* "flash", "ws" or "other".
|
||||
*/
|
||||
_disableFilter: function(type) {
|
||||
// Remove the filter from list of active filters.
|
||||
@@ -1048,7 +1048,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
*
|
||||
* @param string type
|
||||
* Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
|
||||
* "flash" or "other".
|
||||
* "flash", "ws" or "other".
|
||||
*/
|
||||
_enableFilter: function(type) {
|
||||
// Make sure this is a valid filter type.
|
||||
@@ -1100,6 +1100,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
images: this.isImage,
|
||||
media: this.isMedia,
|
||||
flash: this.isFlash,
|
||||
ws: this.isWS,
|
||||
other: this.isOther,
|
||||
freetext: this.isFreetextMatch
|
||||
};
|
||||
@@ -1240,8 +1241,10 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
mimeType.includes("/x-javascript"));
|
||||
},
|
||||
|
||||
isXHR: function({ attachment: { isXHR } }) {
|
||||
return isXHR;
|
||||
isXHR: function(item) {
|
||||
// Show the request it is XHR, except
|
||||
// if the request is a WS upgrade
|
||||
return item.attachment.isXHR && !this.isWS(item);
|
||||
},
|
||||
|
||||
isFont: function({ attachment: { url, mimeType } }) {
|
||||
@@ -1276,6 +1279,36 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
url.includes(".flv");
|
||||
},
|
||||
|
||||
isWS: function({ attachment: { requestHeaders, responseHeaders } }) {
|
||||
// Detect a websocket upgrade if request has an Upgrade header
|
||||
// with value 'websocket'
|
||||
|
||||
if (!requestHeaders || !Array.isArray(requestHeaders.headers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the 'upgrade' header.
|
||||
var upgradeHeader = requestHeaders.headers.find(header => {
|
||||
return (header.name == "Upgrade");
|
||||
});
|
||||
|
||||
// If no header found on request, check response - mainly to get
|
||||
// something we can unit test, as it is impossible to set
|
||||
// the Upgrade header on outgoing XHR as per the spec.
|
||||
if (!upgradeHeader && responseHeaders && Array.isArray(responseHeaders.headers)) {
|
||||
upgradeHeader = responseHeaders.headers.find(header => {
|
||||
return (header.name == "Upgrade");
|
||||
});
|
||||
}
|
||||
|
||||
// Return false if there is no such header or if its value isn't 'websocket'.
|
||||
if (!upgradeHeader || upgradeHeader.value != "websocket") {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isOther: function(e) {
|
||||
return !this.isHtml(e) &&
|
||||
!this.isCss(e) &&
|
||||
@@ -1284,7 +1317,8 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
|
||||
!this.isFont(e) &&
|
||||
!this.isImage(e) &&
|
||||
!this.isMedia(e) &&
|
||||
!this.isFlash(e);
|
||||
!this.isFlash(e) &&
|
||||
!this.isWS(e);
|
||||
},
|
||||
|
||||
isFreetextMatch: function({ attachment: { url } }, text) {
|
||||
@@ -3616,7 +3650,8 @@ PerformanceStatisticsView.prototype = {
|
||||
*/
|
||||
_sanitizeChartDataSource: function(items, emptyCache) {
|
||||
let data = [
|
||||
"html", "css", "js", "xhr", "fonts", "images", "media", "flash", "other"
|
||||
"html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws",
|
||||
"other"
|
||||
].map(e => ({
|
||||
cached: 0,
|
||||
count: 0,
|
||||
@@ -3650,13 +3685,16 @@ PerformanceStatisticsView.prototype = {
|
||||
} else if (RequestsMenuView.prototype.isFlash(requestItem)) {
|
||||
// "flash"
|
||||
type = 7;
|
||||
} else if (RequestsMenuView.prototype.isWS(requestItem)) {
|
||||
// "ws"
|
||||
type = 8;
|
||||
} else if (RequestsMenuView.prototype.isXHR(requestItem)) {
|
||||
// Verify XHR last, to categorize other mime types in their own blobs.
|
||||
// "xhr"
|
||||
type = 3;
|
||||
} else {
|
||||
// "other"
|
||||
type = 8;
|
||||
type = 9;
|
||||
}
|
||||
|
||||
if (emptyCache || !responseIsFresh(details)) {
|
||||
|
||||
@@ -143,6 +143,11 @@
|
||||
data-key="flash"
|
||||
label="&netmonitorUI.footer.filterFlash;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-ws-button"
|
||||
class="requests-menu-filter-button"
|
||||
data-key="ws"
|
||||
label="&netmonitorUI.footer.filterWS;">
|
||||
</button>
|
||||
<button id="requests-menu-filter-other-button"
|
||||
class="requests-menu-filter-button"
|
||||
data-key="other"
|
||||
|
||||
@@ -21,6 +21,11 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
|
||||
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
|
||||
]);
|
||||
|
||||
function test() {
|
||||
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
|
||||
@@ -39,7 +44,7 @@ function test() {
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 8).then(() => {
|
||||
waitForNetworkEvents(aMonitor, 9).then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
@@ -51,83 +56,89 @@ function test() {
|
||||
|
||||
// First test with single filters...
|
||||
testFilterButtons(aMonitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(aMonitor, "html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Reset filters
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
testFilterButtons(aMonitor, "css");
|
||||
return testContents([0, 1, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
|
||||
testFilterButtons(aMonitor, "js");
|
||||
return testContents([0, 0, 1, 0, 0, 0, 0, 0]);
|
||||
return testContents([0, 0, 1, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-xhr-button"));
|
||||
testFilterButtons(aMonitor, "xhr");
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-fonts-button"));
|
||||
testFilterButtons(aMonitor, "fonts");
|
||||
return testContents([0, 0, 0, 1, 0, 0, 0, 0]);
|
||||
return testContents([0, 0, 0, 1, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-images-button"));
|
||||
testFilterButtons(aMonitor, "images");
|
||||
return testContents([0, 0, 0, 0, 1, 0, 0, 0]);
|
||||
return testContents([0, 0, 0, 0, 1, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-media-button"));
|
||||
testFilterButtons(aMonitor, "media");
|
||||
return testContents([0, 0, 0, 0, 0, 1, 1, 0]);
|
||||
return testContents([0, 0, 0, 0, 0, 1, 1, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
testFilterButtons(aMonitor, "flash");
|
||||
return testContents([0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
return testContents([0, 0, 0, 0, 0, 0, 0, 1, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
|
||||
testFilterButtons(aMonitor, "ws");
|
||||
return testContents([0, 0, 0, 0, 0, 0, 0, 0, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testFilterButtons(aMonitor, "all");
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
// Text in filter box that matches nothing should hide all.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("foobar");
|
||||
return testContents([0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Text in filter box that matches should filter out everything else.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("sample");
|
||||
return testContents([1, 1, 1, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Text in filter box that matches should filter out everything else.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("SAMPLE");
|
||||
return testContents([1, 1, 1, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Test negative filtering (only show unmatched items)
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
setFreetextFilter("-sample");
|
||||
return testContents([0, 0, 0, 1, 1, 1, 1, 1]);
|
||||
return testContents([0, 0, 0, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
// ...then combine multiple filters together.
|
||||
.then(() => {
|
||||
@@ -135,43 +146,44 @@ function test() {
|
||||
setFreetextFilter("");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Html and css filter enabled and text filter should show just the html and css match.
|
||||
// Should not show both the items that match the button plus the items that match the text.
|
||||
setFreetextFilter("sample");
|
||||
return testContents([1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
|
||||
.then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
setFreetextFilter("");
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0]);
|
||||
return testContents([1, 1, 0, 0, 0, 0, 0, 1]);
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0]);
|
||||
return testContents([1, 1, 0, 0, 0, 0, 0, 1, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Disable some filters. Only one left active.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-flash-button"));
|
||||
testFilterButtons(aMonitor, "html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
// Disable last active filter. Should toggle to all.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(aMonitor, "all");
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
// Enable few filters and click on all. Only "all" should be checked.
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0]);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testFilterButtons(aMonitor, "all");
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
@@ -261,11 +273,17 @@ function test() {
|
||||
type: "x-shockwave-flash",
|
||||
fullMimeType: "application/x-shockwave-flash"
|
||||
});
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(8),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=ws", {
|
||||
fuzzyUrl: true,
|
||||
status: 101,
|
||||
statusText: "Switching Protocols",
|
||||
});
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
loadCommonFrameScript();
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,6 +21,11 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
|
||||
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
|
||||
]);
|
||||
|
||||
function test() {
|
||||
initNetMonitor(FILTERING_URL).then(([aTab, aDebuggee, aMonitor]) => {
|
||||
info("Starting test... ");
|
||||
@@ -33,7 +38,7 @@ function test() {
|
||||
|
||||
RequestsMenu.lazyUpdate = false;
|
||||
|
||||
waitForNetworkEvents(aMonitor, 8).then(() => {
|
||||
waitForNetworkEvents(aMonitor, 9).then(() => {
|
||||
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
|
||||
|
||||
isnot(RequestsMenu.selectedItem, null,
|
||||
@@ -44,38 +49,38 @@ function test() {
|
||||
"The details pane should not be hidden after toggle button was pressed.");
|
||||
|
||||
testFilterButtons(aMonitor, "all");
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
testContents([1, 1, 1, 1, 1, 1, 1, 1, 1])
|
||||
.then(() => {
|
||||
info("Testing html filtering.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
testFilterButtons(aMonitor, "html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
info("Performing more requests.");
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
|
||||
return waitForNetworkEvents(aMonitor, 8);
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
return waitForNetworkEvents(aMonitor, 9);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering again.");
|
||||
testFilterButtons(aMonitor, "html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
info("Performing more requests.");
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
|
||||
return waitForNetworkEvents(aMonitor, 8);
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
return waitForNetworkEvents(aMonitor, 9);
|
||||
})
|
||||
.then(() => {
|
||||
info("Testing html filtering again.");
|
||||
testFilterButtons(aMonitor, "html");
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]);
|
||||
return testContents([1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
})
|
||||
.then(() => {
|
||||
info("Resetting filters.");
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-all-button"));
|
||||
testFilterButtons(aMonitor, "all");
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
return testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
|
||||
})
|
||||
.then(() => {
|
||||
return teardown(aMonitor);
|
||||
@@ -101,7 +106,7 @@ function test() {
|
||||
"The item at index " + i + " doesn't have the correct hidden state.");
|
||||
}
|
||||
|
||||
for (let i = 0; i < aVisibility.length; i += 8) {
|
||||
for (let i = 0; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=html", {
|
||||
fuzzyUrl: true,
|
||||
@@ -111,7 +116,7 @@ function test() {
|
||||
fullMimeType: "text/html; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 1; i < aVisibility.length; i += 8) {
|
||||
for (let i = 1; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
|
||||
fuzzyUrl: true,
|
||||
@@ -121,7 +126,7 @@ function test() {
|
||||
fullMimeType: "text/css; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 2; i < aVisibility.length; i += 8) {
|
||||
for (let i = 2; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
|
||||
fuzzyUrl: true,
|
||||
@@ -131,7 +136,7 @@ function test() {
|
||||
fullMimeType: "application/javascript; charset=utf-8"
|
||||
});
|
||||
}
|
||||
for (let i = 3; i < aVisibility.length; i += 8) {
|
||||
for (let i = 3; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=font", {
|
||||
fuzzyUrl: true,
|
||||
@@ -141,7 +146,7 @@ function test() {
|
||||
fullMimeType: "font/woff"
|
||||
});
|
||||
}
|
||||
for (let i = 4; i < aVisibility.length; i += 8) {
|
||||
for (let i = 4; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=image", {
|
||||
fuzzyUrl: true,
|
||||
@@ -151,7 +156,7 @@ function test() {
|
||||
fullMimeType: "image/png"
|
||||
});
|
||||
}
|
||||
for (let i = 5; i < aVisibility.length; i += 8) {
|
||||
for (let i = 5; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=audio", {
|
||||
fuzzyUrl: true,
|
||||
@@ -161,7 +166,7 @@ function test() {
|
||||
fullMimeType: "audio/ogg"
|
||||
});
|
||||
}
|
||||
for (let i = 6; i < aVisibility.length; i += 8) {
|
||||
for (let i = 6; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=video", {
|
||||
fuzzyUrl: true,
|
||||
@@ -171,7 +176,7 @@ function test() {
|
||||
fullMimeType: "video/webm"
|
||||
});
|
||||
}
|
||||
for (let i = 7; i < aVisibility.length; i += 8) {
|
||||
for (let i = 7; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=flash", {
|
||||
fuzzyUrl: true,
|
||||
@@ -181,11 +186,19 @@ function test() {
|
||||
fullMimeType: "application/x-shockwave-flash"
|
||||
});
|
||||
}
|
||||
for (let i = 8; i < aVisibility.length; i += 9) {
|
||||
verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
|
||||
"GET", CONTENT_TYPE_SJS + "?fmt=ws", {
|
||||
fuzzyUrl: true,
|
||||
status: 101,
|
||||
statusText: "Switching Protocols"
|
||||
});
|
||||
}
|
||||
|
||||
return promise.resolve(null);
|
||||
}
|
||||
|
||||
loadCommonFrameScript();
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -22,6 +22,11 @@ const REQUESTS_WITH_MEDIA_AND_FLASH = REQUESTS_WITH_MEDIA.concat([
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=flash" },
|
||||
]);
|
||||
|
||||
const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.concat([
|
||||
/* "Upgrade" is a reserved header and can not be set on XMLHttpRequest */
|
||||
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
|
||||
]);
|
||||
|
||||
function test() {
|
||||
Services.prefs.setCharPref("devtools.netmonitor.filters", '["js", "bogus"]');
|
||||
|
||||
@@ -40,7 +45,7 @@ function test() {
|
||||
is(Prefs.filters[1], "bogus",
|
||||
"The second filter type is invalid, but loaded anyway.");
|
||||
|
||||
waitForNetworkEvents(aMonitor, 8).then(() => {
|
||||
waitForNetworkEvents(aMonitor, 9).then(() => {
|
||||
testFilterButtons(aMonitor, "js");
|
||||
ok(true, "Only the correct filter type was taken into consideration.");
|
||||
|
||||
@@ -54,6 +59,6 @@ function test() {
|
||||
});
|
||||
|
||||
loadCommonFrameScript();
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH);
|
||||
performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ function test() {
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-html-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-css-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
|
||||
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
|
||||
testFilterButtonsCustom(aMonitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1]);
|
||||
ok(true, "The correct filtering predicates are used before entering perf. analysis mode.");
|
||||
|
||||
@@ -205,6 +205,14 @@ function handleRequest(request, response) {
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "ws": {
|
||||
response.setStatusLine(request.httpVersion, 101, "Switching Protocols");
|
||||
response.setHeader("Connection", "upgrade", false);
|
||||
response.setHeader("Upgrade", "websocket", false);
|
||||
setCacheHeaders();
|
||||
response.finish();
|
||||
break;
|
||||
}
|
||||
case "gzip": {
|
||||
// Note: we're doing a double gzip encoding to test multiple
|
||||
// converters in network monitor.
|
||||
|
||||
@@ -31,10 +31,22 @@ let App = createClass({
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
},
|
||||
|
||||
onBrowserMounted() {
|
||||
window.postMessage({ type: "browser-mounted" }, "*");
|
||||
},
|
||||
|
||||
onChangeViewportDevice(id, device) {
|
||||
this.props.dispatch(changeDevice(id, device));
|
||||
},
|
||||
|
||||
onContentResize({ width, height }) {
|
||||
window.postMessage({
|
||||
type: "content-resize",
|
||||
width,
|
||||
height,
|
||||
}, "*");
|
||||
},
|
||||
|
||||
onExit() {
|
||||
window.postMessage({ type: "exit" }, "*");
|
||||
},
|
||||
@@ -60,7 +72,9 @@ let App = createClass({
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onExit,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
@@ -81,7 +95,9 @@ let App = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onRotateViewport,
|
||||
onResizeViewport,
|
||||
})
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
* 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/. */
|
||||
|
||||
/* eslint-env browser */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { DOM: dom, createClass, PropTypes, addons } =
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { getToplevelWindow } = require("sdk/window/utils");
|
||||
const { DOM: dom, createClass, addons, PropTypes } =
|
||||
require("devtools/client/shared/vendor/react");
|
||||
|
||||
const Types = require("../types");
|
||||
const { waitForMessage } = require("../utils/e10s");
|
||||
|
||||
module.exports = createClass({
|
||||
|
||||
@@ -15,32 +21,96 @@ module.exports = createClass({
|
||||
|
||||
mixins: [ addons.PureRenderMixin ],
|
||||
|
||||
/**
|
||||
* This component is not allowed to depend directly on frequently changing
|
||||
* data (width, height) due to the use of `dangerouslySetInnerHTML` below.
|
||||
* Any changes in props will cause the <iframe> to be removed and added again,
|
||||
* throwing away the current state of the page.
|
||||
*/
|
||||
propTypes: {
|
||||
location: Types.location.isRequired,
|
||||
width: Types.viewport.width.isRequired,
|
||||
height: Types.viewport.height.isRequired,
|
||||
isResizing: PropTypes.bool.isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
},
|
||||
|
||||
/**
|
||||
* Once the browser element has mounted, load the frame script and enable
|
||||
* various features, like floating scrollbars.
|
||||
*/
|
||||
componentDidMount: Task.async(function*() {
|
||||
let { onContentResize } = this;
|
||||
let browser = this.refs.browserContainer.querySelector("iframe.browser");
|
||||
let mm = browser.frameLoader.messageManager;
|
||||
|
||||
// Notify tests when the content has received a resize event. This is not
|
||||
// quite the same timing as when we _set_ a new size around the browser,
|
||||
// since it still needs to do async work before the content is actually
|
||||
// resized to match.
|
||||
mm.addMessageListener("ResponsiveMode:OnContentResize", onContentResize);
|
||||
|
||||
let ready = waitForMessage(mm, "ResponsiveMode:ChildScriptReady");
|
||||
mm.loadFrameScript("resource://devtools/client/responsivedesign/" +
|
||||
"responsivedesign-child.js", true);
|
||||
yield ready;
|
||||
|
||||
let browserWindow = getToplevelWindow(window);
|
||||
let requiresFloatingScrollbars =
|
||||
!browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
|
||||
let started = waitForMessage(mm, "ResponsiveMode:Start:Done");
|
||||
mm.sendAsyncMessage("ResponsiveMode:Start", {
|
||||
requiresFloatingScrollbars,
|
||||
// Tests expect events on resize to yield on various size changes
|
||||
notifyOnResize: DevToolsUtils.testing,
|
||||
});
|
||||
yield started;
|
||||
|
||||
// manager.js waits for this signal before allowing browser tests to start
|
||||
this.props.onBrowserMounted();
|
||||
}),
|
||||
|
||||
componentWillUnmount() {
|
||||
let { onContentResize } = this;
|
||||
let browser = this.refs.browserContainer.querySelector("iframe.browser");
|
||||
let mm = browser.frameLoader.messageManager;
|
||||
mm.removeMessageListener("ResponsiveMode:OnContentResize", onContentResize);
|
||||
mm.sendAsyncMessage("ResponsiveMode:Stop");
|
||||
},
|
||||
|
||||
onContentResize(msg) {
|
||||
let { onContentResize } = this.props;
|
||||
let { width, height } = msg.data;
|
||||
onContentResize({
|
||||
width,
|
||||
height,
|
||||
});
|
||||
},
|
||||
|
||||
render() {
|
||||
let {
|
||||
location,
|
||||
width,
|
||||
height,
|
||||
isResizing,
|
||||
} = this.props;
|
||||
|
||||
let className = "browser";
|
||||
if (isResizing) {
|
||||
className += " resizing";
|
||||
}
|
||||
// innerHTML expects & to be an HTML entity
|
||||
location = location.replace(/&/g, "&");
|
||||
|
||||
return dom.iframe(
|
||||
return dom.div(
|
||||
{
|
||||
className,
|
||||
src: location,
|
||||
width,
|
||||
height,
|
||||
ref: "browserContainer",
|
||||
className: "browser-container",
|
||||
|
||||
/**
|
||||
* React uses a whitelist for attributes, so we need some way to set
|
||||
* attributes it does not know about, such as @mozbrowser. If this were
|
||||
* the only issue, we could use componentDidMount or ref: node => {} to
|
||||
* set the atttibutes. In the case of @remote, the attribute must be set
|
||||
* before the element is added to the DOM to have any effect, which we
|
||||
* are able to do with this approach.
|
||||
*/
|
||||
dangerouslySetInnerHTML: {
|
||||
__html: `<iframe class="browser" mozbrowser="true" remote="true"
|
||||
noisolation="true" src="${location}"
|
||||
width="100%" height="100%"></iframe>`
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
@@ -26,7 +26,9 @@ module.exports = createClass({
|
||||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
},
|
||||
@@ -115,17 +117,23 @@ module.exports = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
} = this.props;
|
||||
|
||||
let resizeHandleClass = "viewport-resize-handle";
|
||||
|
||||
if (screenshot.isCapturing) {
|
||||
resizeHandleClass += " hidden";
|
||||
}
|
||||
|
||||
let contentClass = "viewport-content";
|
||||
if (this.state.isResizing) {
|
||||
contentClass += " resizing";
|
||||
}
|
||||
|
||||
return dom.div(
|
||||
{
|
||||
className: "resizable-viewport",
|
||||
@@ -137,12 +145,20 @@ module.exports = createClass({
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
}),
|
||||
Browser({
|
||||
location,
|
||||
width: viewport.width,
|
||||
height: viewport.height,
|
||||
isResizing: this.state.isResizing
|
||||
}),
|
||||
dom.div(
|
||||
{
|
||||
className: contentClass,
|
||||
style: {
|
||||
width: viewport.width + "px",
|
||||
height: viewport.height + "px",
|
||||
},
|
||||
},
|
||||
Browser({
|
||||
location,
|
||||
onBrowserMounted,
|
||||
onContentResize,
|
||||
})
|
||||
),
|
||||
dom.div({
|
||||
className: resizeHandleClass,
|
||||
onMouseDown: this.onResizeStart,
|
||||
|
||||
@@ -20,7 +20,9 @@ module.exports = createClass({
|
||||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewport: PropTypes.shape(Types.viewport).isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
},
|
||||
@@ -58,6 +60,8 @@ module.exports = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onContentResize,
|
||||
onBrowserMounted,
|
||||
} = this.props;
|
||||
|
||||
let {
|
||||
@@ -75,7 +79,9 @@ module.exports = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
}),
|
||||
|
||||
@@ -19,7 +19,9 @@ module.exports = createClass({
|
||||
location: Types.location.isRequired,
|
||||
screenshot: PropTypes.shape(Types.screenshot).isRequired,
|
||||
viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
|
||||
onBrowserMounted: PropTypes.func.isRequired,
|
||||
onChangeViewportDevice: PropTypes.func.isRequired,
|
||||
onContentResize: PropTypes.func.isRequired,
|
||||
onResizeViewport: PropTypes.func.isRequired,
|
||||
onRotateViewport: PropTypes.func.isRequired,
|
||||
},
|
||||
@@ -30,7 +32,9 @@ module.exports = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewports,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
} = this.props;
|
||||
@@ -46,7 +50,9 @@ module.exports = createClass({
|
||||
location,
|
||||
screenshot,
|
||||
viewport,
|
||||
onBrowserMounted,
|
||||
onChangeViewportDevice,
|
||||
onContentResize,
|
||||
onResizeViewport,
|
||||
onRotateViewport,
|
||||
});
|
||||
|
||||
@@ -201,19 +201,28 @@ body {
|
||||
background-image: url("./images/rotate-viewport.svg");
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Content
|
||||
*/
|
||||
|
||||
.viewport-content.resizing {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Browser
|
||||
*/
|
||||
|
||||
.browser-container {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.browser {
|
||||
display: block;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.browser.resizing {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* Viewport Resize Handles
|
||||
*/
|
||||
|
||||
@@ -43,7 +43,6 @@ let bootstrap = {
|
||||
this.telemetry.toolOpened("responsive");
|
||||
let store = this.store = Store();
|
||||
let provider = createElement(Provider, { store }, App());
|
||||
|
||||
ReactDOM.render(provider, document.querySelector("#root"));
|
||||
this.initDevices();
|
||||
window.postMessage({ type: "init" }, "*");
|
||||
@@ -116,6 +115,14 @@ window.addInitialViewport = contentURI => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by manager.js when tests want to check the viewport size.
|
||||
*/
|
||||
window.getViewportSize = () => {
|
||||
let { width, height } = bootstrap.store.getState().viewports[0];
|
||||
return { width, height };
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by manager.js to set viewport size from GCLI.
|
||||
*/
|
||||
@@ -126,3 +133,13 @@ window.setViewportSize = (width, height) => {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by manager.js when tests want to use the viewport's message manager.
|
||||
* It is packed into an object because this is the format most easily usable
|
||||
* with ContentTask.spawn().
|
||||
*/
|
||||
window.getViewportMessageManager = () => {
|
||||
let { messageManager } = document.querySelector("iframe.browser").frameLoader;
|
||||
return { messageManager };
|
||||
};
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { Ci, Cr } = require("chrome");
|
||||
const promise = require("promise");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const TOOL_URL = "chrome://devtools/content/responsive.html/index.xhtml";
|
||||
@@ -69,14 +71,14 @@ const ResponsiveUIManager = exports.ResponsiveUIManager = {
|
||||
* @return Promise
|
||||
* Resolved (with no value) when closing is complete.
|
||||
*/
|
||||
closeIfNeeded(window, tab) {
|
||||
closeIfNeeded: Task.async(function*(window, tab) {
|
||||
if (this.isActiveForTab(tab)) {
|
||||
this.activeTabs.get(tab).destroy();
|
||||
yield this.activeTabs.get(tab).destroy();
|
||||
this.activeTabs.delete(tab);
|
||||
this.emit("off", { tab });
|
||||
}
|
||||
return promise.resolve();
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns true if responsive UI is active for a given tab.
|
||||
@@ -195,19 +197,23 @@ ResponsiveUI.prototype = {
|
||||
tabBrowser.loadURI(TOOL_URL);
|
||||
yield tabLoaded(this.tab);
|
||||
let toolWindow = this.toolWindow = tabBrowser.contentWindow;
|
||||
toolWindow.addEventListener("message", this);
|
||||
yield waitForMessage(toolWindow, "init");
|
||||
toolWindow.addInitialViewport(contentURI);
|
||||
toolWindow.addEventListener("message", this);
|
||||
yield waitForMessage(toolWindow, "browser-mounted");
|
||||
}),
|
||||
|
||||
destroy() {
|
||||
destroy: Task.async(function*() {
|
||||
let tabBrowser = this.tab.linkedBrowser;
|
||||
tabBrowser.goBack();
|
||||
this.window = null;
|
||||
let browserWindow = this.browserWindow;
|
||||
this.browserWindow = null;
|
||||
this.tab = null;
|
||||
this.inited = null;
|
||||
this.toolWindow = null;
|
||||
},
|
||||
let loaded = waitForDocLoadComplete(browserWindow.gBrowser);
|
||||
tabBrowser.goBack();
|
||||
yield loaded;
|
||||
}),
|
||||
|
||||
handleEvent(event) {
|
||||
let { tab, window } = this;
|
||||
@@ -218,6 +224,13 @@ ResponsiveUI.prototype = {
|
||||
}
|
||||
|
||||
switch (event.data.type) {
|
||||
case "content-resize":
|
||||
let { width, height } = event.data;
|
||||
this.emit("content-resize", {
|
||||
width,
|
||||
height,
|
||||
});
|
||||
break;
|
||||
case "exit":
|
||||
toolWindow.removeEventListener(event.type, this);
|
||||
ResponsiveUIManager.closeIfNeeded(window, tab);
|
||||
@@ -225,13 +238,23 @@ ResponsiveUI.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
getViewportSize() {
|
||||
return this.toolWindow.getViewportSize();
|
||||
},
|
||||
|
||||
setViewportSize: Task.async(function*(width, height) {
|
||||
yield this.inited;
|
||||
this.toolWindow.setViewportSize(width, height);
|
||||
}),
|
||||
|
||||
getViewportMessageManager() {
|
||||
return this.toolWindow.getViewportMessageManager();
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
EventEmitter.decorate(ResponsiveUI.prototype);
|
||||
|
||||
function waitForMessage(win, type) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
@@ -262,3 +285,27 @@ function tabLoaded(tab) {
|
||||
tab.linkedBrowser.addEventListener("load", handle, true);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the next load to complete in the current browser.
|
||||
*/
|
||||
function waitForDocLoadComplete(gBrowser) {
|
||||
let deferred = promise.defer();
|
||||
let progressListener = {
|
||||
onStateChange: function(webProgress, req, flags, status) {
|
||||
let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
|
||||
Ci.nsIWebProgressListener.STATE_STOP;
|
||||
|
||||
// When a load needs to be retargetted to a new process it is cancelled
|
||||
// with NS_BINDING_ABORTED so ignore that case
|
||||
if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
|
||||
gBrowser.removeProgressListener(progressListener);
|
||||
deferred.resolve();
|
||||
}
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
|
||||
Ci.nsISupportsWeakReference])
|
||||
};
|
||||
gBrowser.addProgressListener(progressListener);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ support-files =
|
||||
!/devtools/client/framework/test/shared-head.js
|
||||
!/devtools/client/framework/test/shared-redux-head.js
|
||||
|
||||
[browser_device_width.js]
|
||||
[browser_exit_button.js]
|
||||
[browser_resize_cmd.js]
|
||||
[browser_screenshot_button.js]
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_URL = "about:logo";
|
||||
|
||||
addRDMTask(TEST_URL, function*({ ui, manager }) {
|
||||
ok(ui, "An instance of the RDM should be attached to the tab.");
|
||||
yield setViewportSize(ui, manager, 110, 500);
|
||||
|
||||
info("Checking initial width/height properties.");
|
||||
yield doInitialChecks(ui);
|
||||
|
||||
info("Changing the RDM size");
|
||||
yield setViewportSize(ui, manager, 90, 500);
|
||||
|
||||
info("Checking for screen props");
|
||||
yield checkScreenProps(ui);
|
||||
|
||||
info("Setting docShell.deviceSizeIsPageSize to false");
|
||||
yield ContentTask.spawn(ui.getViewportMessageManager(), {}, function*() {
|
||||
let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell);
|
||||
docShell.deviceSizeIsPageSize = false;
|
||||
});
|
||||
|
||||
info("Checking for screen props once again.");
|
||||
yield checkScreenProps2(ui);
|
||||
});
|
||||
|
||||
function* doInitialChecks(ui) {
|
||||
let { innerWidth, matchesMedia } = yield grabContentInfo(ui);
|
||||
is(innerWidth, 110, "initial width should be 110px");
|
||||
ok(!matchesMedia, "media query shouldn't match.");
|
||||
}
|
||||
|
||||
function* checkScreenProps(ui) {
|
||||
let { matchesMedia, screen } = yield grabContentInfo(ui);
|
||||
ok(matchesMedia, "media query should match");
|
||||
isnot(window.screen.width, screen.width,
|
||||
"screen.width should not be the size of the screen.");
|
||||
is(screen.width, 90, "screen.width should be the page width");
|
||||
is(screen.height, 500, "screen.height should be the page height");
|
||||
}
|
||||
|
||||
function* checkScreenProps2(ui) {
|
||||
let { matchesMedia, screen } = yield grabContentInfo(ui);
|
||||
ok(!matchesMedia, "media query should be re-evaluated.");
|
||||
is(window.screen.width, screen.width,
|
||||
"screen.width should be the size of the screen.");
|
||||
}
|
||||
|
||||
function grabContentInfo(ui) {
|
||||
return ContentTask.spawn(ui.getViewportMessageManager(), {}, function*() {
|
||||
return {
|
||||
screen: {
|
||||
width: content.screen.width,
|
||||
height: content.screen.height
|
||||
},
|
||||
innerWidth: content.innerWidth,
|
||||
matchesMedia: content.matchMedia("(max-device-width:100px)").matches
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -14,10 +14,9 @@ addRDMTask(TEST_URL, function*({ ui, manager }) {
|
||||
// Wait until the viewport has been added
|
||||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
|
||||
let browser = toolWindow.document.querySelector(".browser");
|
||||
let exitButton = toolWindow.document.getElementById("global-exit-button");
|
||||
|
||||
yield waitForFrameLoad(browser, TEST_URL);
|
||||
yield waitForFrameLoad(ui, TEST_URL);
|
||||
|
||||
ok(manager.isActiveForTab(ui.tab),
|
||||
"Responsive Design Mode active for the tab");
|
||||
|
||||
@@ -15,7 +15,7 @@ add_task(function*() {
|
||||
}
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8,hi";
|
||||
return helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
yield helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup() {
|
||||
@@ -55,6 +55,10 @@ add_task(function*() {
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
yield helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
@@ -93,6 +97,10 @@ add_task(function*() {
|
||||
ok(!isOpen(), "responsive mode is closed");
|
||||
}),
|
||||
},
|
||||
]);
|
||||
});
|
||||
yield helpers.addTabWithToolbar(TEST_URL, (options) => {
|
||||
return helpers.audit(options, [
|
||||
{
|
||||
setup() {
|
||||
done = once(manager, "on");
|
||||
@@ -136,5 +144,5 @@ add_task(function*() {
|
||||
}),
|
||||
},
|
||||
]);
|
||||
}).then(finish);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -17,16 +17,17 @@ addRDMTask(TEST_URL, function*({ ui }) {
|
||||
yield waitUntilState(store, state => state.viewports.length == 1);
|
||||
|
||||
// A single viewport of default size appeared
|
||||
let browser = ui.toolWindow.document.querySelector(".browser");
|
||||
is(browser.width, "320", "Viewport has default width");
|
||||
is(browser.height, "480", "Viewport has default height");
|
||||
let viewport = ui.toolWindow.document.querySelector(".viewport-content");
|
||||
|
||||
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("width"),
|
||||
"320px", "Viewport has default width");
|
||||
is(ui.toolWindow.getComputedStyle(viewport).getPropertyValue("height"),
|
||||
"480px", "Viewport has default height");
|
||||
|
||||
// Browser's location should match original tab
|
||||
// TODO: For the moment, we have parent process <iframe>s and we can just
|
||||
// check the location directly. Bug 1240896 will change this to <iframe
|
||||
// mozbrowser remote>, which is in the child process, so ContentTask or
|
||||
// similar will be needed.
|
||||
yield waitForFrameLoad(browser, TEST_URL);
|
||||
is(browser.contentWindow.location.href, TEST_URL,
|
||||
"Viewport location matches");
|
||||
yield waitForFrameLoad(ui, TEST_URL);
|
||||
let location = yield spawnViewportTask(ui, {}, function*() {
|
||||
return content.location.href;
|
||||
});
|
||||
is(location, TEST_URL, "Viewport location matches");
|
||||
});
|
||||
|
||||
@@ -54,7 +54,7 @@ var openRDM = Task.async(function*(tab) {
|
||||
var closeRDM = Task.async(function*(tab) {
|
||||
info("Closing responsive design mode");
|
||||
let manager = ResponsiveUIManager;
|
||||
manager.closeIfNeeded(window, tab);
|
||||
yield manager.closeIfNeeded(window, tab);
|
||||
info("Responsive design mode closed");
|
||||
});
|
||||
|
||||
@@ -85,12 +85,43 @@ function addRDMTask(url, generator) {
|
||||
});
|
||||
}
|
||||
|
||||
var waitForFrameLoad = Task.async(function*(frame, targetURL) {
|
||||
let window = frame.contentWindow;
|
||||
if ((window.document.readyState == "complete" ||
|
||||
window.document.readyState == "interactive") &&
|
||||
window.location.href == targetURL) {
|
||||
return;
|
||||
function spawnViewportTask(ui, args, task) {
|
||||
return ContentTask.spawn(ui.getViewportMessageManager(), args, task);
|
||||
}
|
||||
|
||||
function waitForFrameLoad(ui, targetURL) {
|
||||
return spawnViewportTask(ui, { targetURL }, function*(args) {
|
||||
if ((content.document.readyState == "complete" ||
|
||||
content.document.readyState == "interactive") &&
|
||||
content.location.href == args.targetURL) {
|
||||
return;
|
||||
}
|
||||
yield ContentTaskUtils.waitForEvent(this, "DOMContentLoaded");
|
||||
});
|
||||
}
|
||||
|
||||
function waitForViewportResizeTo(ui, width, height) {
|
||||
return new Promise(resolve => {
|
||||
let onResize = (_, data) => {
|
||||
if (data.width != width || data.height != height) {
|
||||
return;
|
||||
}
|
||||
ui.off("content-resize", onResize);
|
||||
info(`Got content-resize to ${width} x ${height}`);
|
||||
resolve();
|
||||
};
|
||||
info(`Waiting for content-resize to ${width} x ${height}`);
|
||||
ui.on("content-resize", onResize);
|
||||
});
|
||||
}
|
||||
|
||||
var setViewportSize = Task.async(function*(ui, manager, width, height) {
|
||||
let size = ui.getViewportSize();
|
||||
info(`Current size: ${size.width} x ${size.height}, ` +
|
||||
`set to: ${width} x ${height}`);
|
||||
if (size.width != width || size.height != height) {
|
||||
let resized = waitForViewportResizeTo(ui, width, height);
|
||||
ui.setViewportSize(width, height);
|
||||
yield resized;
|
||||
}
|
||||
yield once(frame, "load");
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const promise = require("promise");
|
||||
|
||||
module.exports = {
|
||||
|
||||
waitForMessage(mm, message) {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let onMessage = event => {
|
||||
mm.removeMessageListener(message, onMessage);
|
||||
deferred.resolve();
|
||||
};
|
||||
mm.addMessageListener(message, onMessage);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
};
|
||||
@@ -5,5 +5,6 @@
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DevToolsModules(
|
||||
'e10s.js',
|
||||
'l10n.js',
|
||||
)
|
||||
|
||||
@@ -836,7 +836,8 @@ StyleEditorUI.prototype = {
|
||||
text(summary, ".stylesheet-linked-file", linkedCSSFile);
|
||||
text(summary, ".stylesheet-title", editor.styleSheet.title || "");
|
||||
text(summary, ".stylesheet-rule-count",
|
||||
PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount));
|
||||
PluralForm.get(ruleCount,
|
||||
getString("ruleCount.label")).replace("#1", ruleCount));
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"_",
|
||||
"getString",
|
||||
"assert",
|
||||
"log",
|
||||
"text",
|
||||
@@ -36,7 +36,7 @@ const gStringBundle = Services.strings.createBundle(PROPERTIES_URL);
|
||||
* Optional arguments to format in the string.
|
||||
* @return string
|
||||
*/
|
||||
function _(name) {
|
||||
function getString(name) {
|
||||
try {
|
||||
if (arguments.length == 1) {
|
||||
return gStringBundle.GetStringFromName(name);
|
||||
@@ -226,8 +226,8 @@ function showFilePicker(path, toSave, parentWindow, callback,
|
||||
fp.defaultString = suggestedFilename;
|
||||
}
|
||||
|
||||
fp.init(parentWindow, _(key + ".title"), mode);
|
||||
fp.appendFilters(_(key + ".filter"), "*.css");
|
||||
fp.init(parentWindow, getString(key + ".title"), mode);
|
||||
fp.appendFilters(getString(key + ".filter"), "*.css");
|
||||
fp.appendFilters(fp.filterAll);
|
||||
fp.open(fpCallback);
|
||||
return;
|
||||
|
||||
@@ -184,12 +184,12 @@ StyleSheetEditor.prototype = {
|
||||
|
||||
if (this._isNew) {
|
||||
let index = this.styleSheet.styleSheetIndex + 1;
|
||||
return _("newStyleSheet", index);
|
||||
return getString("newStyleSheet", index);
|
||||
}
|
||||
|
||||
if (!this.styleSheet.href) {
|
||||
let index = this.styleSheet.styleSheetIndex + 1;
|
||||
return _("inlineStyleSheet", index);
|
||||
return getString("inlineStyleSheet", index);
|
||||
}
|
||||
|
||||
if (!this._friendlyName) {
|
||||
@@ -580,12 +580,8 @@ StyleSheetEditor.prototype = {
|
||||
* @param {Number} y
|
||||
*/
|
||||
_highlightSelectorAt: Task.async(function*(x, y) {
|
||||
// Need to catch parsing exceptions as long as bug 1051900 isn't fixed
|
||||
let info;
|
||||
try {
|
||||
let pos = this.sourceEditor.getPositionFromCoords({left: x, top: y});
|
||||
info = this.sourceEditor.getInfoAt(pos);
|
||||
} catch (e) {}
|
||||
let pos = this.sourceEditor.getPositionFromCoords({left: x, top: y});
|
||||
let info = this.sourceEditor.getInfoAt(pos);
|
||||
if (!info || info.state !== "selector") {
|
||||
return;
|
||||
}
|
||||
@@ -751,12 +747,13 @@ StyleSheetEditor.prototype = {
|
||||
*/
|
||||
_getKeyBindings: function() {
|
||||
let bindings = {};
|
||||
let keybind = Editor.accel(getString("saveStyleSheet.commandkey"));
|
||||
|
||||
bindings[Editor.accel(_("saveStyleSheet.commandkey"))] = () => {
|
||||
bindings[keybind] = () => {
|
||||
this.saveToFile(this.savedFile);
|
||||
};
|
||||
|
||||
bindings["Shift-" + Editor.accel(_("saveStyleSheet.commandkey"))] = () => {
|
||||
bindings["Shift-" + keybind] = () => {
|
||||
this.saveToFile();
|
||||
};
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ StyleEditorPanel.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
let errorMessage = _(data.key);
|
||||
let errorMessage = getString(data.key);
|
||||
if (data.append) {
|
||||
errorMessage += " " + data.append;
|
||||
}
|
||||
|
||||
@@ -1655,13 +1655,15 @@ bool
|
||||
TabChild::RecvSizeModeChanged(const nsSizeMode& aSizeMode)
|
||||
{
|
||||
mPuppetWidget->SetSizeMode(aSizeMode);
|
||||
if (!mPuppetWidget->IsVisible()) {
|
||||
return true;
|
||||
}
|
||||
nsCOMPtr<nsIDocument> document(GetDocument());
|
||||
nsCOMPtr<nsIPresShell> presShell = document->GetShell();
|
||||
if (presShell) {
|
||||
nsPresContext* presContext = presShell->GetPresContext();
|
||||
if (presContext) {
|
||||
presContext->MediaFeatureValuesChangedAllDocuments(eRestyle_Subtree,
|
||||
NS_STYLE_HINT_REFLOW);
|
||||
presContext->SizeModeChanged(aSizeMode);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -118,7 +118,7 @@ PresentationConnection::State() const
|
||||
|
||||
void
|
||||
PresentationConnection::Send(const nsAString& aData,
|
||||
ErrorResult& aRv)
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
// Sending is not allowed if the session is not connected.
|
||||
if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
|
||||
@@ -126,21 +126,6 @@ PresentationConnection::Send(const nsAString& aData,
|
||||
return;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIStringInputStream> stream =
|
||||
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
|
||||
if(NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 msgString(aData);
|
||||
rv = stream->SetData(msgString.BeginReading(), msgString.Length());
|
||||
if(NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPresentationService> service =
|
||||
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
||||
if(NS_WARN_IF(!service)) {
|
||||
@@ -148,7 +133,7 @@ PresentationConnection::Send(const nsAString& aData,
|
||||
return;
|
||||
}
|
||||
|
||||
rv = service->SendSessionMessage(mId, stream);
|
||||
nsresult rv = service->SendSessionMessage(mId, aData);
|
||||
if(NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
@@ -186,7 +171,7 @@ PresentationConnection::Terminate(ErrorResult& aRv)
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationConnection::NotifyStateChange(const nsAString& aSessionId,
|
||||
uint16_t aState)
|
||||
uint16_t aState)
|
||||
{
|
||||
if (!aSessionId.Equals(mId)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
@@ -233,7 +218,7 @@ PresentationConnection::NotifyStateChange(const nsAString& aSessionId,
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationConnection::NotifyMessage(const nsAString& aSessionId,
|
||||
const nsACString& aData)
|
||||
const nsACString& aData)
|
||||
{
|
||||
if (!aSessionId.Equals(mId)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
// Bug 1228209 - plan to remove this eventually
|
||||
function log(aMsg) {
|
||||
//dump("-*- PresentationDataChannelSessionTransport.js : " + aMsg + "\n");
|
||||
}
|
||||
|
||||
const PRESENTATIONTRANSPORT_CID = Components.ID("{dd2bbf2f-3399-4389-8f5f-d382afb8b2d6}");
|
||||
const PRESENTATIONTRANSPORT_CONTRACTID = "mozilla.org/presentation/datachanneltransport;1";
|
||||
|
||||
const PRESENTATIONTRANSPORTBUILDER_CID = Components.ID("{215b2f62-46e2-4004-a3d1-6858e56c20f3}");
|
||||
const PRESENTATIONTRANSPORTBUILDER_CONTRACTID = "mozilla.org/presentation/datachanneltransportbuilder;1";
|
||||
|
||||
|
||||
function PresentationDataChannelDescription(aDataChannelSDP) {
|
||||
this._dataChannelSDP = JSON.stringify(aDataChannelSDP);
|
||||
}
|
||||
|
||||
PresentationDataChannelDescription.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
|
||||
get type() {
|
||||
return nsIPresentationChannelDescription.TYPE_DATACHANNEL;
|
||||
},
|
||||
get tcpAddress() {
|
||||
return null;
|
||||
},
|
||||
get tcpPort() {
|
||||
return null;
|
||||
},
|
||||
get dataChannelSDP() {
|
||||
return this._dataChannelSDP;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function PresentationTransportBuilder() {
|
||||
log("PresentationTransportBuilder construct");
|
||||
this._isControlChannelNeeded = true;
|
||||
}
|
||||
|
||||
PresentationTransportBuilder.prototype = {
|
||||
classID: PRESENTATIONTRANSPORTBUILDER_CID,
|
||||
contractID: PRESENTATIONTRANSPORTBUILDER_CONTRACTID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDataChannelSessionTransportBuilder,
|
||||
Ci.nsIPresentationControlChannelListener,
|
||||
Ci.nsITimerCallback]),
|
||||
|
||||
buildDataChannelTransport: function(aType, aWindow, aControlChannel, aListener) {
|
||||
if (!aType || !aWindow || !aControlChannel || !aListener) {
|
||||
log("buildDataChannelTransport with illegal parameters");
|
||||
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
if (this._window) {
|
||||
log("buildDataChannelTransport has started.");
|
||||
throw Cr.NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
log("buildDataChannelTransport with type " + aType);
|
||||
this._type = aType;
|
||||
this._window = aWindow;
|
||||
this._controlChannel = aControlChannel.QueryInterface(Ci.nsIPresentationControlChannel);
|
||||
this._controlChannel.listener = this;
|
||||
this._listener = aListener.QueryInterface(Ci.nsIPresentationSessionTransportBuilderListener);
|
||||
|
||||
// TODO bug 1227053 set iceServers from |nsIPresentationDevice|
|
||||
this._peerConnection = new this._window.RTCPeerConnection();
|
||||
|
||||
// |this._controlChannel == null| will throw since the control channel is
|
||||
// abnormally closed.
|
||||
this._peerConnection.onicecandidate = aEvent => aEvent.candidate &&
|
||||
this._controlChannel.sendIceCandidate(JSON.stringify(aEvent.candidate));
|
||||
|
||||
this._peerConnection.onnegotiationneeded = () => {
|
||||
log("onnegotiationneeded with type " + this._type);
|
||||
this._peerConnection.createOffer()
|
||||
.then(aOffer => this._peerConnection.setLocalDescription(aOffer))
|
||||
.then(() => this._controlChannel
|
||||
.sendOffer(new PresentationDataChannelDescription(this._peerConnection.localDescription)))
|
||||
.catch(e => this._reportError(e));
|
||||
}
|
||||
|
||||
switch (this._type) {
|
||||
case Ci.nsIPresentationSessionTransportBuilder.TYPE_SENDER:
|
||||
this._dataChannel = this._peerConnection.createDataChannel("presentationAPI");
|
||||
this._setDataChannel();
|
||||
break;
|
||||
|
||||
case Ci.nsIPresentationSessionTransportBuilder.TYPE_RECEIVER:
|
||||
this._peerConnection.ondatachannel = aEvent => {
|
||||
this._dataChannel = aEvent.channel;
|
||||
this._setDataChannel();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw Cr.NS_ERROR_ILLEGAL_VALUE;
|
||||
}
|
||||
|
||||
// TODO bug 1228235 we should have a way to let device providers customize
|
||||
// the time-out duration.
|
||||
let timeout;
|
||||
try {
|
||||
timeout = Services.prefs.getIntPref("presentation.receiver.loading.timeout");
|
||||
} catch (e) {
|
||||
// This happens if the pref doesn't exist, so we have a default value.
|
||||
timeout = 10000;
|
||||
}
|
||||
|
||||
// The timer is to check if the negotiation finishes on time.
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.initWithCallback(this, timeout, this._timer.TYPE_ONE_SHOT);
|
||||
},
|
||||
|
||||
notify: function() {
|
||||
if (!this._sessionTransport) {
|
||||
this._cleanup(Cr.NS_ERROR_NET_TIMEOUT);
|
||||
}
|
||||
},
|
||||
|
||||
_reportError: function(aError) {
|
||||
log("report Error " + aError.name + ":" + aError.message);
|
||||
this._cleanup(Cr.NS_ERROR_FAILURE);
|
||||
},
|
||||
|
||||
_setDataChannel: function() {
|
||||
this._dataChannel.onopen = () => {
|
||||
log("data channel is open, notify the listener, type " + this._type);
|
||||
|
||||
// Handoff the ownership of _peerConnection and _dataChannel to
|
||||
// _sessionTransport
|
||||
this._sessionTransport = new PresentationTransport();
|
||||
this._sessionTransport.init(this._peerConnection, this._dataChannel);
|
||||
this._peerConnection = this._dataChannel = null;
|
||||
|
||||
this._listener.onSessionTransport(this._sessionTransport);
|
||||
this._sessionTransport.callback.notifyTransportReady();
|
||||
|
||||
this._cleanup(Cr.NS_OK);
|
||||
};
|
||||
|
||||
this._dataChannel.onerror = aError => {
|
||||
log("data channel onerror " + aError.name + ":" + aError.message);
|
||||
this._cleanup(Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
},
|
||||
|
||||
_cleanup: function(aReason) {
|
||||
if (aReason != Cr.NS_OK) {
|
||||
this._listener.onError(aReason);
|
||||
}
|
||||
|
||||
if (this._dataChannel) {
|
||||
this._dataChannel.close();
|
||||
this._dataChannel = null;
|
||||
}
|
||||
|
||||
if (this._peerConnection) {
|
||||
this._peerConnection.close();
|
||||
this._peerConnection = null;
|
||||
}
|
||||
|
||||
this._type = null;
|
||||
this._window = null;
|
||||
|
||||
if (this._controlChannel) {
|
||||
this._controlChannel.close(aReason);
|
||||
this._controlChannel = null;
|
||||
}
|
||||
|
||||
this._listener = null;
|
||||
this._sessionTransport = null;
|
||||
|
||||
if (this._timer) {
|
||||
this._timer.cancel();
|
||||
this._timer = null;
|
||||
}
|
||||
},
|
||||
|
||||
// nsIPresentationControlChannelListener
|
||||
onOffer: function(aOffer) {
|
||||
if (this._type !== Ci.nsIPresentationSessionTransportBuilder.TYPE_RECEIVER ||
|
||||
this._sessionTransport) {
|
||||
log("onOffer status error");
|
||||
this._cleanup(Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
log("onOffer: " + aOffer.dataChannelSDP + " with type " + this._type);
|
||||
|
||||
let offer = new this._window
|
||||
.RTCSessionDescription(JSON.parse(aOffer.dataChannelSDP));
|
||||
|
||||
this._peerConnection.setRemoteDescription(offer)
|
||||
.then(() => this._peerConnection.signalingState == "stable" ||
|
||||
this._peerConnection.createAnswer())
|
||||
.then(aAnswer => this._peerConnection.setLocalDescription(aAnswer))
|
||||
.then(() => {
|
||||
this._isControlChannelNeeded = false;
|
||||
this._controlChannel
|
||||
.sendAnswer(new PresentationDataChannelDescription(this._peerConnection.localDescription))
|
||||
}).catch(e => this._reportError(e));
|
||||
},
|
||||
|
||||
onAnswer: function(aAnswer) {
|
||||
if (this._type !== Ci.nsIPresentationSessionTransportBuilder.TYPE_SENDER ||
|
||||
this._sessionTransport) {
|
||||
log("onAnswer status error");
|
||||
this._cleanup(Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
|
||||
log("onAnswer: " + aAnswer.dataChannelSDP + " with type " + this._type);
|
||||
|
||||
let answer = new this._window
|
||||
.RTCSessionDescription(JSON.parse(aAnswer.dataChannelSDP));
|
||||
|
||||
this._peerConnection.setRemoteDescription(answer).catch(e => this._reportError(e));
|
||||
this._isControlChannelNeeded = false;
|
||||
},
|
||||
|
||||
onIceCandidate: function(aCandidate) {
|
||||
log("onIceCandidate: " + aCandidate + " with type " + this._type);
|
||||
let candidate = new this._window.RTCIceCandidate(JSON.parse(aCandidate));
|
||||
this._peerConnection.addIceCandidate(candidate).catch(e => this._reportError(e));
|
||||
},
|
||||
|
||||
notifyOpened: function() {
|
||||
log("notifyOpened, should be opened beforehand");
|
||||
},
|
||||
|
||||
notifyClosed: function(aReason) {
|
||||
log("notifyClosed reason: " + aReason);
|
||||
|
||||
if (aReason != Cr.NS_OK) {
|
||||
this._cleanup(aReason);
|
||||
} else if (this._isControlChannelNeeded) {
|
||||
this._cleanup(Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
this._controlChannel = null;
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
function PresentationTransport() {
|
||||
this._messageQueue = [];
|
||||
this._closeReason = Cr.NS_OK;
|
||||
}
|
||||
|
||||
PresentationTransport.prototype = {
|
||||
classID: PRESENTATIONTRANSPORT_CID,
|
||||
contractID: PRESENTATIONTRANSPORT_CONTRACTID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport]),
|
||||
|
||||
init: function(aPeerConnection, aDataChannel) {
|
||||
log("initWithDataChannel");
|
||||
this._enableDataNotification = false;
|
||||
this._dataChannel = aDataChannel;
|
||||
this._peerConnection = aPeerConnection;
|
||||
|
||||
this._dataChannel.onopen = () => {
|
||||
log("data channel reopen. Should never touch here");
|
||||
};
|
||||
|
||||
this._dataChannel.onclose = () => {
|
||||
log("data channel onclose");
|
||||
if (this._callback) {
|
||||
this._callback.notifyTransportClosed(this._closeReason);
|
||||
}
|
||||
this._cleanup();
|
||||
}
|
||||
|
||||
this._dataChannel.onmessage = aEvent => {
|
||||
log("data channel onmessage " + aEvent.data);
|
||||
|
||||
if (!this._enableDataNotification || !this._callback) {
|
||||
log("queue message");
|
||||
this._messageQueue.push(aEvent.data);
|
||||
return;
|
||||
}
|
||||
this._callback.notifyData(aEvent.data);
|
||||
};
|
||||
|
||||
|
||||
this._dataChannel.onerror = aError => {
|
||||
log("data channel onerror " + aError.name + ":" + aError.message);
|
||||
if (this._callback) {
|
||||
this._callback.notifyTransportClosed(Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
this._cleanup();
|
||||
}
|
||||
},
|
||||
|
||||
// nsIPresentationTransport
|
||||
get selfAddress() {
|
||||
throw NS_ERROR_NOT_AVAILABLE;
|
||||
},
|
||||
|
||||
get callback() {
|
||||
return this._callback;
|
||||
},
|
||||
|
||||
set callback(aCallback) {
|
||||
this._callback = aCallback;
|
||||
},
|
||||
|
||||
send: function(aData) {
|
||||
log("send " + aData);
|
||||
this._dataChannel.send(aData);
|
||||
},
|
||||
|
||||
enableDataNotification: function() {
|
||||
log("enableDataNotification");
|
||||
if (this._enableDataNotification) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._callback) {
|
||||
throw NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
this._enableDataNotification = true;
|
||||
|
||||
this._messageQueue.forEach(aData => this._callback.notifyData(aData));
|
||||
this._messageQueue = [];
|
||||
},
|
||||
|
||||
close: function(aReason) {
|
||||
this._closeReason = aReason;
|
||||
|
||||
this._dataChannel.close();
|
||||
},
|
||||
|
||||
_cleanup: function() {
|
||||
this._dataChannel = null;
|
||||
|
||||
if (this._peerConnection) {
|
||||
this._peerConnection.close();
|
||||
this._peerConnection = null;
|
||||
}
|
||||
this._callback = null;
|
||||
this._messageQueue = [];
|
||||
},
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationTransportBuilder,
|
||||
PresentationTransport]);
|
||||
@@ -0,0 +1,6 @@
|
||||
# PresentationDataChannelSessionTransport.js
|
||||
component {dd2bbf2f-3399-4389-8f5f-d382afb8b2d6} PresentationDataChannelSessionTransport.js
|
||||
contract @mozilla.org/presentation/datachanneltransport;1 {dd2bbf2f-3399-4389-8f5f-d382afb8b2d6}
|
||||
|
||||
component {215b2f62-46e2-4004-a3d1-6858e56c20f3} PresentationDataChannelSessionTransport.js
|
||||
contract @mozilla.org/presentation/datachanneltransportbuilder;1 {215b2f62-46e2-4004-a3d1-6858e56c20f3}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_PresentationLog_h
|
||||
#define mozilla_dom_PresentationLog_h
|
||||
|
||||
/*
|
||||
* NSPR_LOG_MODULES=Presentation:5
|
||||
* For detail, see PresentationService.cpp
|
||||
*/
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
extern mozilla::LazyLogModule gPresentationLog;
|
||||
}
|
||||
}
|
||||
|
||||
#undef PRES_ERROR
|
||||
#define PRES_ERROR(...) MOZ_LOG(mozilla::dom::gPresentationLog, mozilla::LogLevel::Error, (__VA_ARGS__))
|
||||
|
||||
#undef PRES_DEBUG
|
||||
#define PRES_DEBUG(...) MOZ_LOG(mozilla::dom::gPresentationLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
|
||||
|
||||
#endif // mozilla_dom_PresentationLog_h
|
||||
@@ -80,6 +80,13 @@ PresentationRequest::WrapObject(JSContext* aCx,
|
||||
|
||||
already_AddRefed<Promise>
|
||||
PresentationRequest::Start(ErrorResult& aRv)
|
||||
{
|
||||
return StartWithDevice(NullString(), aRv);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
PresentationRequest::StartWithDevice(const nsAString& aDeviceId,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
|
||||
if (NS_WARN_IF(!global)) {
|
||||
@@ -124,7 +131,7 @@ PresentationRequest::Start(ErrorResult& aRv)
|
||||
|
||||
nsCOMPtr<nsIPresentationServiceCallback> callback =
|
||||
new PresentationRequesterCallback(this, mUrl, id, promise);
|
||||
rv = service->StartSession(mUrl, id, origin, callback);
|
||||
rv = service->StartSession(mUrl, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ public:
|
||||
// WebIDL (public APIs)
|
||||
already_AddRefed<Promise> Start(ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> StartWithDevice(const nsAString& aDeviceId,
|
||||
ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise> GetAvailability(ErrorResult& aRv);
|
||||
|
||||
IMPL_EVENT_HANDLER(connectionavailable);
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "PresentationLog.h"
|
||||
#include "PresentationService.h"
|
||||
|
||||
using namespace mozilla;
|
||||
@@ -48,6 +49,8 @@ private:
|
||||
nsString mOrigin;
|
||||
};
|
||||
|
||||
LazyLogModule gPresentationLog("Presentation");
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
@@ -384,6 +387,8 @@ NS_IMETHODIMP
|
||||
PresentationService::StartSession(const nsAString& aUrl,
|
||||
const nsAString& aSessionId,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aDeviceId,
|
||||
uint64_t aWindowId,
|
||||
nsIPresentationServiceCallback* aCallback)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
@@ -396,28 +401,77 @@ PresentationService::StartSession(const nsAString& aUrl,
|
||||
new PresentationControllingInfo(aUrl, aSessionId, aCallback);
|
||||
mSessionInfo.Put(aSessionId, info);
|
||||
|
||||
// Pop up a prompt and ask user to select a device.
|
||||
nsCOMPtr<nsIPresentationDevicePrompt> prompt =
|
||||
do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
|
||||
if (NS_WARN_IF(!prompt)) {
|
||||
return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
// Only track the info when an actual window ID, which would never be 0, is
|
||||
// provided (for an in-process sender page).
|
||||
if (aWindowId != 0) {
|
||||
mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
|
||||
mRespondingWindowIds.Put(aSessionId, aWindowId);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPresentationDeviceRequest> request =
|
||||
new PresentationDeviceRequest(aUrl, aSessionId, aOrigin);
|
||||
nsresult rv = prompt->PromptDeviceSelection(request);
|
||||
|
||||
if (aDeviceId.IsVoid()) {
|
||||
// Pop up a prompt and ask user to select a device.
|
||||
nsCOMPtr<nsIPresentationDevicePrompt> prompt =
|
||||
do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
|
||||
if (NS_WARN_IF(!prompt)) {
|
||||
return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
nsresult rv = prompt->PromptDeviceSelection(request);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Find the designated device from available device list.
|
||||
nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
|
||||
do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
|
||||
if (NS_WARN_IF(!deviceManager)) {
|
||||
return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIArray> devices;
|
||||
nsresult rv = deviceManager->GetAvailableDevices(getter_AddRefs(devices));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
nsCOMPtr<nsISimpleEnumerator> enumerator;
|
||||
rv = devices->Enumerate(getter_AddRefs(enumerator));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return info->ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 utf8DeviceId(aDeviceId);
|
||||
bool hasMore;
|
||||
while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore){
|
||||
nsCOMPtr<nsISupports> isupports;
|
||||
rv = enumerator->GetNext(getter_AddRefs(isupports));
|
||||
|
||||
nsCOMPtr<nsIPresentationDevice> device(do_QueryInterface(isupports));
|
||||
MOZ_ASSERT(device);
|
||||
|
||||
nsAutoCString id;
|
||||
if (NS_SUCCEEDED(device->GetId(id)) && id.Equals(utf8DeviceId)) {
|
||||
request->Select(device);
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Reject if designated device is not available.
|
||||
return info->ReplyError(NS_ERROR_DOM_NOT_FOUND_ERR);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationService::SendSessionMessage(const nsAString& aSessionId,
|
||||
nsIInputStream* aStream)
|
||||
const nsAString& aData)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aStream);
|
||||
MOZ_ASSERT(!aData.IsEmpty());
|
||||
MOZ_ASSERT(!aSessionId.IsEmpty());
|
||||
|
||||
RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
|
||||
@@ -425,7 +479,7 @@ PresentationService::SendSessionMessage(const nsAString& aSessionId,
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
return info->Send(aStream);
|
||||
return info->Send(aData);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -568,7 +622,7 @@ PresentationService::NotifyReceiverReady(const nsAString& aSessionId,
|
||||
// Only track the responding info when an actual window ID, which would never
|
||||
// be 0, is provided (for an in-process receiver page).
|
||||
if (aWindowId != 0) {
|
||||
mRespondingSessionIds.Put(aWindowId, new nsAutoString(aSessionId));
|
||||
mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
|
||||
mRespondingWindowIds.Put(aSessionId, aWindowId);
|
||||
}
|
||||
|
||||
@@ -583,7 +637,7 @@ PresentationService::UntrackSessionInfo(const nsAString& aSessionId)
|
||||
|
||||
// Remove the in-process responding info if there's still any.
|
||||
uint64_t windowId = 0;
|
||||
if(mRespondingWindowIds.Get(aSessionId, &windowId)) {
|
||||
if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
|
||||
mRespondingWindowIds.Remove(aSessionId);
|
||||
mRespondingSessionIds.Remove(windowId);
|
||||
}
|
||||
@@ -591,6 +645,16 @@ PresentationService::UntrackSessionInfo(const nsAString& aSessionId)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationService::GetWindowIdBySessionId(const nsAString& aSessionId,
|
||||
uint64_t* aWindowId)
|
||||
{
|
||||
if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
bool
|
||||
PresentationService::IsSessionAccessible(const nsAString& aSessionId,
|
||||
base::ProcessId aProcessId)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/Services.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsGlobalWindow.h"
|
||||
#include "nsIDocShell.h"
|
||||
#include "nsIFrameLoader.h"
|
||||
#include "nsIMutableArray.h"
|
||||
@@ -22,6 +23,7 @@
|
||||
#include "nsNetCID.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "PresentationLog.h"
|
||||
#include "PresentationService.h"
|
||||
#include "PresentationSessionInfo.h"
|
||||
|
||||
@@ -38,13 +40,6 @@ using namespace mozilla;
|
||||
using namespace mozilla::dom;
|
||||
using namespace mozilla::services;
|
||||
|
||||
|
||||
static LazyLogModule gPresentationSessionInfoLog("PresentationSessionInfo");
|
||||
|
||||
#undef LOG
|
||||
#define LOG(...) MOZ_LOG(gPresentationSessionInfoLog, mozilla::LogLevel::Error, (__VA_ARGS__))
|
||||
|
||||
|
||||
/*
|
||||
* Implementation of PresentationChannelDescription
|
||||
*/
|
||||
@@ -105,7 +100,7 @@ PresentationNetworkHelper::GetWifiIPAddress()
|
||||
NS_IMETHODIMP
|
||||
PresentationNetworkHelper::OnError(const nsACString & aReason)
|
||||
{
|
||||
LOG("PresentationNetworkHelper::OnError: %s",
|
||||
PRES_ERROR("PresentationNetworkHelper::OnError: %s",
|
||||
nsPromiseFlatCString(aReason).get());
|
||||
return NS_OK;
|
||||
}
|
||||
@@ -127,21 +122,21 @@ PresentationNetworkHelper::OnGetWifiIPAddress(const nsACString& aIPAddress)
|
||||
|
||||
#endif // MOZ_WIDGET_ANDROID
|
||||
|
||||
class PresentationChannelDescription final : public nsIPresentationChannelDescription
|
||||
class TCPPresentationChannelDescription final : public nsIPresentationChannelDescription
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION
|
||||
|
||||
PresentationChannelDescription(const nsACString& aAddress,
|
||||
uint16_t aPort)
|
||||
TCPPresentationChannelDescription(const nsACString& aAddress,
|
||||
uint16_t aPort)
|
||||
: mAddress(aAddress)
|
||||
, mPort(aPort)
|
||||
{
|
||||
}
|
||||
|
||||
private:
|
||||
~PresentationChannelDescription() {}
|
||||
~TCPPresentationChannelDescription() {}
|
||||
|
||||
nsCString mAddress;
|
||||
uint16_t mPort;
|
||||
@@ -150,23 +145,21 @@ private:
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
NS_IMPL_ISUPPORTS(PresentationChannelDescription, nsIPresentationChannelDescription)
|
||||
NS_IMPL_ISUPPORTS(TCPPresentationChannelDescription, nsIPresentationChannelDescription)
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationChannelDescription::GetType(uint8_t* aRetVal)
|
||||
TCPPresentationChannelDescription::GetType(uint8_t* aRetVal)
|
||||
{
|
||||
if (NS_WARN_IF(!aRetVal)) {
|
||||
return NS_ERROR_INVALID_POINTER;
|
||||
}
|
||||
|
||||
// TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
// Only support TCP socket for now.
|
||||
*aRetVal = nsIPresentationChannelDescription::TYPE_TCP;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal)
|
||||
TCPPresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal)
|
||||
{
|
||||
if (NS_WARN_IF(!aRetVal)) {
|
||||
return NS_ERROR_INVALID_POINTER;
|
||||
@@ -177,11 +170,9 @@ PresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
// Ultimately we may use all the available addresses. DataChannel appears
|
||||
// more robust upon handling ICE. And at the first stage Presentation API is
|
||||
// only exposed on Firefox OS where the first IP appears enough for most
|
||||
// scenarios.
|
||||
// TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
|
||||
// into account. And at the first stage Presentation API is only exposed on
|
||||
// Firefox OS where the first IP appears enough for most scenarios.
|
||||
nsCOMPtr<nsISupportsCString> address = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
|
||||
if (NS_WARN_IF(!address)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
@@ -195,7 +186,7 @@ PresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationChannelDescription::GetTcpPort(uint16_t* aRetVal)
|
||||
TCPPresentationChannelDescription::GetTcpPort(uint16_t* aRetVal)
|
||||
{
|
||||
if (NS_WARN_IF(!aRetVal)) {
|
||||
return NS_ERROR_INVALID_POINTER;
|
||||
@@ -206,10 +197,8 @@ PresentationChannelDescription::GetTcpPort(uint16_t* aRetVal)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP)
|
||||
TCPPresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP)
|
||||
{
|
||||
// TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
// Only support TCP socket for now.
|
||||
aDataChannelSDP.Truncate();
|
||||
return NS_OK;
|
||||
}
|
||||
@@ -220,7 +209,8 @@ PresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP)
|
||||
|
||||
NS_IMPL_ISUPPORTS(PresentationSessionInfo,
|
||||
nsIPresentationSessionTransportCallback,
|
||||
nsIPresentationControlChannelListener);
|
||||
nsIPresentationControlChannelListener,
|
||||
nsIPresentationSessionTransportBuilderListener);
|
||||
|
||||
/* virtual */ nsresult
|
||||
PresentationSessionInfo::Init(nsIPresentationControlChannel* aControlChannel)
|
||||
@@ -246,6 +236,8 @@ PresentationSessionInfo::Shutdown(nsresult aReason)
|
||||
}
|
||||
|
||||
mIsResponderReady = false;
|
||||
|
||||
mBuilder = nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -271,7 +263,7 @@ PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationSessionInfo::Send(nsIInputStream* aData)
|
||||
PresentationSessionInfo::Send(const nsAString& aData)
|
||||
{
|
||||
if (NS_WARN_IF(!IsSessionReady())) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
@@ -338,6 +330,22 @@ PresentationSessionInfo::UntrackFromService()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsPIDOMWindowInner*
|
||||
PresentationSessionInfo::GetWindow()
|
||||
{
|
||||
nsCOMPtr<nsIPresentationService> service =
|
||||
do_GetService(PRESENTATION_SERVICE_CONTRACTID);
|
||||
if (NS_WARN_IF(!service)) {
|
||||
return nullptr;
|
||||
}
|
||||
uint64_t windowId = 0;
|
||||
if (NS_WARN_IF(NS_FAILED(service->GetWindowIdBySessionId(mSessionId, &windowId)))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return nsGlobalWindow::GetInnerWindowWithId(windowId)->AsInner();
|
||||
}
|
||||
|
||||
/* virtual */ bool
|
||||
PresentationSessionInfo::IsAccessible(base::ProcessId aProcessId)
|
||||
{
|
||||
@@ -412,7 +420,31 @@ PresentationSessionInfo::NotifyData(const nsACString& aData)
|
||||
return mListener->NotifyMessage(mSessionId, aData);
|
||||
}
|
||||
|
||||
/*
|
||||
// nsIPresentationSessionTransportBuilderListener
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
|
||||
{
|
||||
mTransport = transport;
|
||||
|
||||
nsresult rv = mTransport->SetCallback(this);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (mListener) {
|
||||
mTransport->EnableDataNotification();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionInfo::OnError(nsresult reason)
|
||||
{
|
||||
return ReplyError(reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of PresentationControllingInfo
|
||||
*
|
||||
* During presentation session establishment, the sender expects the following
|
||||
@@ -460,6 +492,12 @@ PresentationControllingInfo::Init(nsIPresentationControlChannel* aControlChannel
|
||||
return rv;
|
||||
}
|
||||
|
||||
int32_t port;
|
||||
rv = mServerSocket->GetPort(&port);
|
||||
if (!NS_WARN_IF(NS_FAILED(rv))) {
|
||||
PRES_DEBUG("%s:ServerSocket created.port[%d]\n",__func__, port);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -501,11 +539,10 @@ PresentationControllingInfo::GetAddress()
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
// Ultimately we may use all the available addresses. DataChannel appears
|
||||
// more robust upon handling ICE. And at the first stage Presentation API is
|
||||
// only exposed on Firefox OS where the first IP appears enough for most
|
||||
// scenarios.
|
||||
// TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
|
||||
// into account. And at the first stage Presentation API is only exposed on
|
||||
// Firefox OS where the first IP appears enough for most scenarios.
|
||||
|
||||
nsAutoString ip;
|
||||
ip.Assign(ips[0]);
|
||||
|
||||
@@ -530,6 +567,14 @@ PresentationControllingInfo::GetAddress()
|
||||
return rv;
|
||||
}
|
||||
|
||||
#elif defined(MOZ_MULET)
|
||||
// In simulator,we need to use the "127.0.0.1" as target address.
|
||||
NS_DispatchToMainThread(
|
||||
NS_NewRunnableMethodWithArg<nsCString>(
|
||||
this,
|
||||
&PresentationControllingInfo::OnGetAddress,
|
||||
"127.0.0.1"));
|
||||
|
||||
#else
|
||||
// TODO Get host IP via other platforms.
|
||||
|
||||
@@ -562,8 +607,8 @@ PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
|
||||
return rv;
|
||||
}
|
||||
|
||||
RefPtr<PresentationChannelDescription> description =
|
||||
new PresentationChannelDescription(aAddress, static_cast<uint16_t>(port));
|
||||
RefPtr<TCPPresentationChannelDescription> description =
|
||||
new TCPPresentationChannelDescription(aAddress, static_cast<uint16_t>(port));
|
||||
return mControlChannel->SendOffer(description);
|
||||
}
|
||||
|
||||
@@ -599,7 +644,27 @@ NS_IMETHODIMP
|
||||
PresentationControllingInfo::NotifyOpened()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
return GetAddress();
|
||||
|
||||
if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) {
|
||||
// Build TCP session transport
|
||||
return GetAddress();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
|
||||
do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1");
|
||||
|
||||
if (NS_WARN_IF(!builder)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
mBuilder = builder;
|
||||
mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
|
||||
|
||||
return builder->BuildDataChannelTransport(nsIPresentationSessionTransportBuilder::TYPE_SENDER,
|
||||
GetWindow(),
|
||||
mControlChannel,
|
||||
this);
|
||||
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -628,25 +693,23 @@ NS_IMETHODIMP
|
||||
PresentationControllingInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
|
||||
nsISocketTransport* aTransport)
|
||||
{
|
||||
int32_t port;
|
||||
nsresult rv = aTransport->GetPort(&port);
|
||||
if (!NS_WARN_IF(NS_FAILED(rv))) {
|
||||
PRES_DEBUG("%s:receive from port[%d]\n",__func__, port);
|
||||
}
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Initialize |mTransport| and use |this| as the callback.
|
||||
mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID);
|
||||
if (NS_WARN_IF(!mTransport)) {
|
||||
// Initialize session transport builder and use |this| as the callback.
|
||||
nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
|
||||
do_CreateInstance(PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID);
|
||||
if (NS_WARN_IF(!builder)) {
|
||||
return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
|
||||
}
|
||||
|
||||
nsresult rv = mTransport->InitWithSocketTransport(aTransport, this);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Enable data notification if the listener has been registered.
|
||||
if (mListener) {
|
||||
return mTransport->EnableDataNotification();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
|
||||
return builder->BuildTCPSenderTransport(aTransport, this);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -672,7 +735,7 @@ PresentationControllingInfo::OnStopListening(nsIServerSocket* aServerSocket,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* Implementation of PresentationPresentingInfo
|
||||
*
|
||||
* During presentation session establishment, the receiver expects the following
|
||||
@@ -731,49 +794,106 @@ PresentationPresentingInfo::Shutdown(nsresult aReason)
|
||||
mPromise = nullptr;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationPresentingInfo::InitTransportAndSendAnswer()
|
||||
// nsIPresentationSessionTransportBuilderListener
|
||||
NS_IMETHODIMP
|
||||
PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
|
||||
{
|
||||
// Establish a data transport channel |mTransport| to the sender and use
|
||||
// |this| as the callback.
|
||||
mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID);
|
||||
if (NS_WARN_IF(!mTransport)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
nsresult rv = PresentationSessionInfo::OnSessionTransport(transport);
|
||||
|
||||
nsresult rv = mTransport->InitWithChannelDescription(mRequesterDescription, this);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Enable data notification if the listener has been registered.
|
||||
if (mListener) {
|
||||
rv = mTransport->EnableDataNotification();
|
||||
// send answer for TCP session transport
|
||||
if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
|
||||
// Prepare and send the answer.
|
||||
// In the current implementation of |PresentationSessionTransport|,
|
||||
// |GetSelfAddress| cannot return the real info when it's initialized via
|
||||
// |buildTCPReceiverTransport|. Yet this deficiency only affects the channel
|
||||
// description for the answer, which is not actually checked at requester side.
|
||||
nsCOMPtr<nsINetAddr> selfAddr;
|
||||
rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr));
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
|
||||
nsCString address;
|
||||
uint16_t port = 0;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
selfAddr->GetAddress(address);
|
||||
selfAddr->GetPort(&port);
|
||||
}
|
||||
nsCOMPtr<nsIPresentationChannelDescription> description =
|
||||
new TCPPresentationChannelDescription(address, port);
|
||||
|
||||
return mControlChannel->SendAnswer(description);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationPresentingInfo::OnError(nsresult reason)
|
||||
{
|
||||
return PresentationSessionInfo::OnError(reason);
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationPresentingInfo::InitTransportAndSendAnswer()
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
uint8_t type = 0;
|
||||
nsresult rv = mRequesterDescription->GetType(&type);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (type == nsIPresentationChannelDescription::TYPE_TCP) {
|
||||
// Establish a data transport channel |mTransport| to the sender and use
|
||||
// |this| as the callback.
|
||||
nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
|
||||
do_CreateInstance(PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID);
|
||||
if (NS_WARN_IF(!builder)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
mBuilder = builder;
|
||||
mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
|
||||
return builder->BuildTCPReceiverTransport(mRequesterDescription, this);
|
||||
}
|
||||
|
||||
if (type == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
||||
if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
|
||||
do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1");
|
||||
|
||||
if (NS_WARN_IF(!builder)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
mBuilder = builder;
|
||||
mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
|
||||
rv = builder->BuildDataChannelTransport(nsIPresentationSessionTransportBuilder::TYPE_RECEIVER,
|
||||
GetWindow(),
|
||||
mControlChannel,
|
||||
this);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// delegate |onOffer| to builder
|
||||
nsCOMPtr<nsIPresentationControlChannelListener> listener(do_QueryInterface(builder));
|
||||
|
||||
if (NS_WARN_IF(!listener)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
return listener->OnOffer(mRequesterDescription);
|
||||
}
|
||||
|
||||
// Prepare and send the answer.
|
||||
// TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
// In the current implementation of |PresentationSessionTransport|,
|
||||
// |GetSelfAddress| cannot return the real info when it's initialized via
|
||||
// |InitWithChannelDescription|. Yet this deficiency only affects the channel
|
||||
// description for the answer, which is not actually checked at requester side.
|
||||
nsCOMPtr<nsINetAddr> selfAddr;
|
||||
rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr));
|
||||
NS_WARN_IF(NS_FAILED(rv));
|
||||
|
||||
nsCString address;
|
||||
uint16_t port = 0;
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
selfAddr->GetAddress(address);
|
||||
selfAddr->GetPort(&port);
|
||||
}
|
||||
nsCOMPtr<nsIPresentationChannelDescription> description =
|
||||
new PresentationChannelDescription(address, port);
|
||||
|
||||
return mControlChannel->SendAnswer(description);
|
||||
MOZ_ASSERT(false, "Unknown nsIPresentationChannelDescription type!");
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -904,7 +1024,7 @@ PresentationPresentingInfo::Notify(nsITimer* aTimer)
|
||||
// PromiseNativeHandler
|
||||
void
|
||||
PresentationPresentingInfo::ResolvedCallback(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue)
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
@@ -968,7 +1088,7 @@ PresentationPresentingInfo::ResolvedCallback(JSContext* aCx,
|
||||
|
||||
void
|
||||
PresentationPresentingInfo::RejectedCallback(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aValue)
|
||||
JS::Handle<JS::Value> aValue)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_WARNING("Launching the receiver page has been rejected.");
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "nsIPresentationListener.h"
|
||||
#include "nsIPresentationService.h"
|
||||
#include "nsIPresentationSessionTransport.h"
|
||||
#include "nsIPresentationSessionTransportBuilder.h"
|
||||
#include "nsIServerSocket.h"
|
||||
#include "nsITimer.h"
|
||||
#include "nsString.h"
|
||||
@@ -28,10 +29,12 @@ namespace dom {
|
||||
|
||||
class PresentationSessionInfo : public nsIPresentationSessionTransportCallback
|
||||
, public nsIPresentationControlChannelListener
|
||||
, public nsIPresentationSessionTransportBuilderListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK
|
||||
NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER
|
||||
|
||||
PresentationSessionInfo(const nsAString& aUrl,
|
||||
const nsAString& aSessionId,
|
||||
@@ -89,7 +92,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
nsresult Send(nsIInputStream* aData);
|
||||
nsresult Send(const nsAString& aData);
|
||||
|
||||
nsresult Close(nsresult aReason,
|
||||
uint32_t aState);
|
||||
@@ -108,10 +111,7 @@ protected:
|
||||
|
||||
nsresult ReplySuccess();
|
||||
|
||||
bool IsSessionReady()
|
||||
{
|
||||
return mIsResponderReady && mIsTransportReady;
|
||||
}
|
||||
virtual bool IsSessionReady() = 0;
|
||||
|
||||
virtual nsresult UntrackFromService();
|
||||
|
||||
@@ -130,6 +130,11 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
// Should be nsIPresentationChannelDescription::TYPE_TCP/TYPE_DATACHANNEL
|
||||
uint8_t mTransportType = 0;
|
||||
|
||||
nsPIDOMWindowInner* GetWindow();
|
||||
|
||||
nsString mUrl;
|
||||
nsString mSessionId;
|
||||
bool mIsResponderReady;
|
||||
@@ -140,6 +145,7 @@ protected:
|
||||
nsCOMPtr<nsIPresentationDevice> mDevice;
|
||||
nsCOMPtr<nsIPresentationSessionTransport> mTransport;
|
||||
nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
|
||||
nsCOMPtr<nsIPresentationSessionTransportBuilder> mBuilder;
|
||||
};
|
||||
|
||||
// Session info with controlling browsing context (sender side) behaviors.
|
||||
@@ -174,6 +180,18 @@ private:
|
||||
nsresult OnGetAddress(const nsACString& aAddress);
|
||||
|
||||
nsCOMPtr<nsIServerSocket> mServerSocket;
|
||||
|
||||
protected:
|
||||
bool IsSessionReady() override
|
||||
{
|
||||
if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
|
||||
return mIsResponderReady && mIsTransportReady;
|
||||
} else if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
|
||||
// Established RTCDataChannel implies responder is ready.
|
||||
return mIsTransportReady;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Session info with presenting browsing context (receiver side) behaviors.
|
||||
@@ -184,6 +202,7 @@ class PresentationPresentingInfo final : public PresentationSessionInfo
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
|
||||
NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
|
||||
PresentationPresentingInfo(const nsAString& aUrl,
|
||||
@@ -232,6 +251,12 @@ private:
|
||||
// The content parent communicating with the content process which the OOP
|
||||
// receiver page belongs to.
|
||||
nsCOMPtr<nsIContentParent> mContentParent;
|
||||
|
||||
protected:
|
||||
bool IsSessionReady() override
|
||||
{
|
||||
return mIsResponderReady && mIsTransportReady;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
||||
+149
-79
@@ -16,10 +16,12 @@
|
||||
#include "nsISocketTransportService.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsQueryObject.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "PresentationSessionTransport.h"
|
||||
#include "PresentationLog.h"
|
||||
#include "PresentationTCPSessionTransport.h"
|
||||
|
||||
#define BUFFER_SIZE 65536
|
||||
|
||||
@@ -29,7 +31,7 @@ using namespace mozilla::dom;
|
||||
class CopierCallbacks final : public nsIRequestObserver
|
||||
{
|
||||
public:
|
||||
explicit CopierCallbacks(PresentationSessionTransport* aTransport)
|
||||
explicit CopierCallbacks(PresentationTCPSessionTransport* aTransport)
|
||||
: mOwner(aTransport)
|
||||
{}
|
||||
|
||||
@@ -38,7 +40,7 @@ public:
|
||||
private:
|
||||
~CopierCallbacks() {}
|
||||
|
||||
RefPtr<PresentationSessionTransport> mOwner;
|
||||
RefPtr<PresentationTCPSessionTransport> mOwner;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver)
|
||||
@@ -56,76 +58,87 @@ CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsre
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION(PresentationSessionTransport, mTransport,
|
||||
NS_IMPL_CYCLE_COLLECTION(PresentationTCPSessionTransport, mTransport,
|
||||
mSocketInputStream, mSocketOutputStream,
|
||||
mInputStreamPump, mInputStreamScriptable,
|
||||
mMultiplexStream, mMultiplexStreamCopier, mCallback)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(PresentationSessionTransport)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(PresentationSessionTransport)
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(PresentationTCPSessionTransport)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(PresentationTCPSessionTransport)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationSessionTransport)
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationTCPSessionTransport)
|
||||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPresentationSessionTransport)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransport)
|
||||
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIPresentationTCPSessionTransportBuilder)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
PresentationSessionTransport::PresentationSessionTransport()
|
||||
: mReadyState(CLOSED)
|
||||
PresentationTCPSessionTransport::PresentationTCPSessionTransport()
|
||||
: mReadyState(ReadyState::CLOSED)
|
||||
, mAsyncCopierActive(false)
|
||||
, mCloseStatus(NS_OK)
|
||||
, mDataNotificationEnabled(false)
|
||||
{
|
||||
}
|
||||
|
||||
PresentationSessionTransport::~PresentationSessionTransport()
|
||||
PresentationTCPSessionTransport::~PresentationTCPSessionTransport()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::InitWithSocketTransport(nsISocketTransport* aTransport,
|
||||
nsIPresentationSessionTransportCallback* aCallback)
|
||||
PresentationTCPSessionTransport::BuildTCPSenderTransport(nsISocketTransport* aTransport,
|
||||
nsIPresentationSessionTransportBuilderListener* aListener)
|
||||
{
|
||||
if (NS_WARN_IF(!aCallback)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mCallback = aCallback;
|
||||
|
||||
if (NS_WARN_IF(!aTransport)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mTransport = aTransport;
|
||||
|
||||
if (NS_WARN_IF(!aListener)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mListener = aListener;
|
||||
|
||||
nsresult rv = CreateStream();
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
SetReadyState(OPEN);
|
||||
mType = nsIPresentationSessionTransportBuilder::TYPE_SENDER;
|
||||
|
||||
if (IsReadyToNotifyData()) {
|
||||
return CreateInputStreamPump();
|
||||
}
|
||||
nsCOMPtr<nsIPresentationSessionTransport> sessionTransport = do_QueryObject(this);
|
||||
nsCOMPtr<nsIRunnable> onSessionTransportRunnable =
|
||||
NS_NewRunnableMethodWithArgs
|
||||
<nsIPresentationSessionTransport*>(mListener,
|
||||
&nsIPresentationSessionTransportBuilderListener::OnSessionTransport,
|
||||
sessionTransport);
|
||||
|
||||
return NS_OK;
|
||||
NS_DispatchToCurrentThread(onSessionTransportRunnable);
|
||||
|
||||
|
||||
nsCOMPtr<nsIRunnable> setReadyStateRunnable =
|
||||
NS_NewRunnableMethodWithArgs<ReadyState>(this,
|
||||
&PresentationTCPSessionTransport::SetReadyState,
|
||||
ReadyState::OPEN);
|
||||
return NS_DispatchToCurrentThread(setReadyStateRunnable);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelDescription* aDescription,
|
||||
nsIPresentationSessionTransportCallback* aCallback)
|
||||
PresentationTCPSessionTransport::BuildTCPReceiverTransport(nsIPresentationChannelDescription* aDescription,
|
||||
nsIPresentationSessionTransportBuilderListener* aListener)
|
||||
{
|
||||
if (NS_WARN_IF(!aCallback)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mCallback = aCallback;
|
||||
|
||||
if (NS_WARN_IF(!aDescription)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!aListener)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
mListener = aListener;
|
||||
|
||||
uint16_t serverPort;
|
||||
nsresult rv = aDescription->GetTcpPort(&serverPort);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@@ -138,11 +151,9 @@ PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelD
|
||||
return rv;
|
||||
}
|
||||
|
||||
// TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
// Ultimately we may use all the available addresses. DataChannel appears
|
||||
// more robust upon handling ICE. And at the first stage Presentation API is
|
||||
// only exposed on Firefox OS where the first IP appears enough for most
|
||||
// scenarios.
|
||||
// TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
|
||||
// into account. And at the first stage Presentation API is only exposed on
|
||||
// Firefox OS where the first IP appears enough for most scenarios.
|
||||
nsCOMPtr<nsISupportsCString> supportStr = do_QueryElementAt(serverHosts, 0);
|
||||
if (NS_WARN_IF(!supportStr)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
@@ -154,7 +165,9 @@ PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelD
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
SetReadyState(CONNECTING);
|
||||
PRES_DEBUG("%s:ServerHost[%s],ServerPort[%d]\n", __func__, serverHost.get(), serverPort);
|
||||
|
||||
SetReadyState(ReadyState::CONNECTING);
|
||||
|
||||
nsCOMPtr<nsISocketTransportService> sts =
|
||||
do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
|
||||
@@ -177,11 +190,19 @@ PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelD
|
||||
return rv;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
mType = nsIPresentationSessionTransportBuilder::TYPE_RECEIVER;
|
||||
|
||||
nsCOMPtr<nsIPresentationSessionTransport> sessionTransport = do_QueryObject(this);
|
||||
nsCOMPtr<nsIRunnable> runnable =
|
||||
NS_NewRunnableMethodWithArgs
|
||||
<nsIPresentationSessionTransport*>(mListener,
|
||||
&nsIPresentationSessionTransportBuilderListener::OnSessionTransport,
|
||||
sessionTransport);
|
||||
return NS_DispatchToCurrentThread(runnable);
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationSessionTransport::CreateStream()
|
||||
PresentationTCPSessionTransport::CreateStream()
|
||||
{
|
||||
nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@@ -249,8 +270,12 @@ PresentationSessionTransport::CreateStream()
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationSessionTransport::CreateInputStreamPump()
|
||||
PresentationTCPSessionTransport::CreateInputStreamPump()
|
||||
{
|
||||
if (NS_WARN_IF(mInputStreamPump)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult rv;
|
||||
mInputStreamPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
@@ -271,7 +296,7 @@ PresentationSessionTransport::CreateInputStreamPump()
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::EnableDataNotification()
|
||||
PresentationTCPSessionTransport::EnableDataNotification()
|
||||
{
|
||||
if (NS_WARN_IF(!mCallback)) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
@@ -290,8 +315,24 @@ PresentationSessionTransport::EnableDataNotification()
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIPresentationSessionTransportBuilderListener
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress)
|
||||
PresentationTCPSessionTransport::GetCallback(nsIPresentationSessionTransportCallback** aCallback)
|
||||
{
|
||||
nsCOMPtr<nsIPresentationSessionTransportCallback> callback = mCallback;
|
||||
callback.forget(aCallback);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationTCPSessionTransport::SetCallback(nsIPresentationSessionTransportCallback* aCallback)
|
||||
{
|
||||
mCallback = aCallback;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationTCPSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress)
|
||||
{
|
||||
if (NS_WARN_IF(!mTransport)) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
@@ -301,7 +342,7 @@ PresentationSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress)
|
||||
}
|
||||
|
||||
void
|
||||
PresentationSessionTransport::EnsureCopying()
|
||||
PresentationTCPSessionTransport::EnsureCopying()
|
||||
{
|
||||
if (mAsyncCopierActive) {
|
||||
return;
|
||||
@@ -313,14 +354,14 @@ PresentationSessionTransport::EnsureCopying()
|
||||
}
|
||||
|
||||
void
|
||||
PresentationSessionTransport::NotifyCopyComplete(nsresult aStatus)
|
||||
PresentationTCPSessionTransport::NotifyCopyComplete(nsresult aStatus)
|
||||
{
|
||||
mAsyncCopierActive = false;
|
||||
mMultiplexStream->RemoveStream(0);
|
||||
if (NS_WARN_IF(NS_FAILED(aStatus))) {
|
||||
if (mReadyState != CLOSED) {
|
||||
if (mReadyState != ReadyState::CLOSED) {
|
||||
mCloseStatus = aStatus;
|
||||
SetReadyState(CLOSED);
|
||||
SetReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -336,21 +377,34 @@ PresentationSessionTransport::NotifyCopyComplete(nsresult aStatus)
|
||||
return;
|
||||
}
|
||||
|
||||
if (mReadyState == CLOSING) {
|
||||
if (mReadyState == ReadyState::CLOSING) {
|
||||
mSocketOutputStream->Close();
|
||||
mCloseStatus = NS_OK;
|
||||
SetReadyState(CLOSED);
|
||||
SetReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::Send(nsIInputStream* aData)
|
||||
PresentationTCPSessionTransport::Send(const nsAString& aData)
|
||||
{
|
||||
if (NS_WARN_IF(mReadyState != OPEN)) {
|
||||
if (NS_WARN_IF(mReadyState != ReadyState::OPEN)) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
mMultiplexStream->AppendStream(aData);
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIStringInputStream> stream =
|
||||
do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv);
|
||||
if(NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
NS_ConvertUTF16toUTF8 msgString(aData);
|
||||
rv = stream->SetData(msgString.BeginReading(), msgString.Length());
|
||||
if(NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NS_ERROR_DOM_INVALID_STATE_ERR;
|
||||
}
|
||||
|
||||
mMultiplexStream->AppendStream(stream);
|
||||
|
||||
EnsureCopying();
|
||||
|
||||
@@ -358,14 +412,16 @@ PresentationSessionTransport::Send(nsIInputStream* aData)
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::Close(nsresult aReason)
|
||||
PresentationTCPSessionTransport::Close(nsresult aReason)
|
||||
{
|
||||
if (mReadyState == CLOSED || mReadyState == CLOSING) {
|
||||
PRES_DEBUG("%s:reason[%x]\n", __func__, aReason);
|
||||
|
||||
if (mReadyState == ReadyState::CLOSED || mReadyState == ReadyState::CLOSING) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
mCloseStatus = aReason;
|
||||
SetReadyState(CLOSING);
|
||||
SetReadyState(ReadyState::CLOSING);
|
||||
|
||||
uint32_t count = 0;
|
||||
mMultiplexStream->GetCount(&count);
|
||||
@@ -376,18 +432,32 @@ PresentationSessionTransport::Close(nsresult aReason)
|
||||
mSocketInputStream->Close();
|
||||
mDataNotificationEnabled = false;
|
||||
|
||||
mListener = nullptr;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
PresentationSessionTransport::SetReadyState(ReadyState aReadyState)
|
||||
PresentationTCPSessionTransport::SetReadyState(ReadyState aReadyState)
|
||||
{
|
||||
mReadyState = aReadyState;
|
||||
|
||||
if (mReadyState == OPEN && mCallback) {
|
||||
if (mReadyState == ReadyState::OPEN) {
|
||||
if (IsReadyToNotifyData()) {
|
||||
CreateInputStreamPump();
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!mCallback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the transport channel is ready.
|
||||
NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady()));
|
||||
} else if (mReadyState == CLOSED && mCallback) {
|
||||
} else if (mReadyState == ReadyState::CLOSED && mCallback) {
|
||||
if (NS_WARN_IF(!mCallback)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Notify the transport channel has been shut down.
|
||||
NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportClosed(mCloseStatus)));
|
||||
mCallback = nullptr;
|
||||
@@ -396,29 +466,27 @@ PresentationSessionTransport::SetReadyState(ReadyState aReadyState)
|
||||
|
||||
// nsITransportEventSink
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::OnTransportStatus(nsITransport* aTransport,
|
||||
nsresult aStatus,
|
||||
int64_t aProgress,
|
||||
int64_t aProgressMax)
|
||||
PresentationTCPSessionTransport::OnTransportStatus(nsITransport* aTransport,
|
||||
nsresult aStatus,
|
||||
int64_t aProgress,
|
||||
int64_t aProgressMax)
|
||||
{
|
||||
PRES_DEBUG("%s:aStatus[%x]\n", __func__, aStatus);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (aStatus != NS_NET_STATUS_CONNECTED_TO) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
SetReadyState(OPEN);
|
||||
|
||||
if (IsReadyToNotifyData()) {
|
||||
return CreateInputStreamPump();
|
||||
}
|
||||
SetReadyState(ReadyState::OPEN);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIInputStreamCallback
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream)
|
||||
PresentationTCPSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
@@ -426,9 +494,9 @@ PresentationSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream)
|
||||
uint64_t dummy;
|
||||
nsresult rv = aStream->Available(&dummy);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
if (mReadyState != CLOSED) {
|
||||
if (mReadyState != ReadyState::CLOSED) {
|
||||
mCloseStatus = NS_ERROR_CONNECTION_REFUSED;
|
||||
SetReadyState(CLOSED);
|
||||
SetReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,18 +505,20 @@ PresentationSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream)
|
||||
|
||||
// nsIRequestObserver
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::OnStartRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext)
|
||||
PresentationTCPSessionTransport::OnStartRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext)
|
||||
{
|
||||
// Do nothing.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::OnStopRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatusCode)
|
||||
PresentationTCPSessionTransport::OnStopRequest(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsresult aStatusCode)
|
||||
{
|
||||
PRES_DEBUG("%s:aStatusCode[%x]\n", __func__, aStatusCode);
|
||||
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
uint32_t count;
|
||||
@@ -468,20 +538,20 @@ PresentationSessionTransport::OnStopRequest(nsIRequest* aRequest,
|
||||
}
|
||||
|
||||
// We call this even if there is no error.
|
||||
if (mReadyState != CLOSED) {
|
||||
if (mReadyState != ReadyState::CLOSED) {
|
||||
mCloseStatus = aStatusCode;
|
||||
SetReadyState(CLOSED);
|
||||
SetReadyState(ReadyState::CLOSED);
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// nsIStreamListener
|
||||
NS_IMETHODIMP
|
||||
PresentationSessionTransport::OnDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
PresentationTCPSessionTransport::OnDataAvailable(nsIRequest* aRequest,
|
||||
nsISupports* aContext,
|
||||
nsIInputStream* aStream,
|
||||
uint64_t aOffset,
|
||||
uint32_t aCount)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
+16
-14
@@ -11,6 +11,7 @@
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIAsyncInputStream.h"
|
||||
#include "nsIPresentationSessionTransport.h"
|
||||
#include "nsIPresentationSessionTransportBuilder.h"
|
||||
#include "nsIStreamListener.h"
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsITransport.h"
|
||||
@@ -32,34 +33,32 @@ namespace dom {
|
||||
* presenting receiver side. The lifetime is managed in either
|
||||
* |PresentationControllingInfo| (sender side) or |PresentationPresentingInfo|
|
||||
* (receiver side) in PresentationSessionInfo.cpp.
|
||||
*
|
||||
* TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
|
||||
* The implementation over the TCP channel is primarily used for the early stage
|
||||
* of Presentation API (without SSL) and should be migrated to DataChannel with
|
||||
* full support soon.
|
||||
*/
|
||||
class PresentationSessionTransport final : public nsIPresentationSessionTransport
|
||||
, public nsITransportEventSink
|
||||
, public nsIInputStreamCallback
|
||||
, public nsIStreamListener
|
||||
class PresentationTCPSessionTransport final : public nsIPresentationSessionTransport
|
||||
, public nsIPresentationTCPSessionTransportBuilder
|
||||
, public nsITransportEventSink
|
||||
, public nsIInputStreamCallback
|
||||
, public nsIStreamListener
|
||||
{
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PresentationSessionTransport,
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PresentationTCPSessionTransport,
|
||||
nsIPresentationSessionTransport)
|
||||
|
||||
NS_DECL_NSIPRESENTATIONSESSIONTRANSPORT
|
||||
NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDER
|
||||
NS_DECL_NSIPRESENTATIONTCPSESSIONTRANSPORTBUILDER
|
||||
NS_DECL_NSITRANSPORTEVENTSINK
|
||||
NS_DECL_NSIINPUTSTREAMCALLBACK
|
||||
NS_DECL_NSIREQUESTOBSERVER
|
||||
NS_DECL_NSISTREAMLISTENER
|
||||
|
||||
PresentationSessionTransport();
|
||||
PresentationTCPSessionTransport();
|
||||
|
||||
void NotifyCopyComplete(nsresult aStatus);
|
||||
|
||||
private:
|
||||
~PresentationSessionTransport();
|
||||
~PresentationTCPSessionTransport();
|
||||
|
||||
nsresult CreateStream();
|
||||
|
||||
@@ -67,7 +66,7 @@ private:
|
||||
|
||||
void EnsureCopying();
|
||||
|
||||
enum ReadyState {
|
||||
enum class ReadyState {
|
||||
CONNECTING,
|
||||
OPEN,
|
||||
CLOSING,
|
||||
@@ -78,7 +77,7 @@ private:
|
||||
|
||||
bool IsReadyToNotifyData()
|
||||
{
|
||||
return mDataNotificationEnabled && mReadyState == OPEN;
|
||||
return mDataNotificationEnabled && mReadyState == ReadyState::OPEN;
|
||||
}
|
||||
|
||||
ReadyState mReadyState;
|
||||
@@ -86,6 +85,8 @@ private:
|
||||
nsresult mCloseStatus;
|
||||
bool mDataNotificationEnabled;
|
||||
|
||||
uint8_t mType = 0;
|
||||
|
||||
// Raw socket streams
|
||||
nsCOMPtr<nsISocketTransport> mTransport;
|
||||
nsCOMPtr<nsIInputStream> mSocketInputStream;
|
||||
@@ -100,6 +101,7 @@ private:
|
||||
nsCOMPtr<nsIAsyncStreamCopier> mMultiplexStreamCopier;
|
||||
|
||||
nsCOMPtr<nsIPresentationSessionTransportCallback> mCallback;
|
||||
nsCOMPtr<nsIPresentationSessionTransportBuilderListener> mListener;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
@@ -15,6 +15,7 @@ XPIDL_SOURCES += [
|
||||
'nsIPresentationService.idl',
|
||||
'nsIPresentationSessionRequest.idl',
|
||||
'nsIPresentationSessionTransport.idl',
|
||||
'nsIPresentationSessionTransportBuilder.idl',
|
||||
'nsITCPPresentationServer.idl',
|
||||
]
|
||||
|
||||
|
||||
@@ -67,7 +67,8 @@ interface nsIPresentationControlChannelListener: nsISupports
|
||||
|
||||
/*
|
||||
* The control channel for establishing RTCPeerConnection for a presentation
|
||||
* session. SDP Offer/Answer will be exchanged through this interface.
|
||||
* session. SDP Offer/Answer will be exchanged through this interface. The
|
||||
* control channel should be in-order.
|
||||
*/
|
||||
[scriptable, uuid(e60e208c-a9f5-4bc6-9a3e-47f3e4ae9c57)]
|
||||
interface nsIPresentationControlChannel: nsISupports
|
||||
|
||||
@@ -39,6 +39,6 @@ interface nsIPresentationRespondingListener : nsISupports
|
||||
/*
|
||||
* Called when an incoming session connects.
|
||||
*/
|
||||
void notifySessionConnect(in uint64_t windowId,
|
||||
void notifySessionConnect(in unsigned long long windowId,
|
||||
in DOMString sessionId);
|
||||
};
|
||||
|
||||
@@ -33,7 +33,7 @@ interface nsIPresentationServiceCallback : nsISupports
|
||||
void notifyError(in nsresult error);
|
||||
};
|
||||
|
||||
[scriptable, uuid(c177a13a-bf1a-48bf-8032-d415c3343c46)]
|
||||
[scriptable, uuid(de42b741-5619-4650-b961-c2cebb572c95)]
|
||||
interface nsIPresentationService : nsISupports
|
||||
{
|
||||
/*
|
||||
@@ -43,6 +43,12 @@ interface nsIPresentationService : nsISupports
|
||||
* @param url: The url of presenting page.
|
||||
* @param sessionId: An ID to identify presentation session.
|
||||
* @param origin: The url of requesting page.
|
||||
* @param deviceId: The specified device of handling this request, null string
|
||||
for prompt device selection dialog.
|
||||
* @param windowId: The inner window ID associated with the presentation
|
||||
* session. (0 implies no window ID since no actual window
|
||||
* uses 0 as its ID. Generally it's the case the window is
|
||||
* located in different process from this service)
|
||||
* @param callback: Invoke the callback when the operation is completed.
|
||||
* NotifySuccess() is called with |id| if a session is
|
||||
* established successfully with the selected device.
|
||||
@@ -51,16 +57,18 @@ interface nsIPresentationService : nsISupports
|
||||
void startSession(in DOMString url,
|
||||
in DOMString sessionId,
|
||||
in DOMString origin,
|
||||
in DOMString deviceId,
|
||||
in unsigned long long windowId,
|
||||
in nsIPresentationServiceCallback callback);
|
||||
|
||||
/*
|
||||
* Send the message wrapped with an input stream to the session.
|
||||
* Send the message to the session.
|
||||
*
|
||||
* @param sessionId: An ID to identify presentation session.
|
||||
* @param stream: The message is converted to an input stream.
|
||||
* @param data: the message being sent out.
|
||||
*/
|
||||
void sendSessionMessage(in DOMString sessionId,
|
||||
in nsIInputStream stream);
|
||||
in DOMString data);
|
||||
|
||||
/*
|
||||
* Close the session.
|
||||
@@ -111,14 +119,14 @@ interface nsIPresentationService : nsISupports
|
||||
* @param windowId: The window ID associated with the listener.
|
||||
* @param listener: The listener to register.
|
||||
*/
|
||||
void registerRespondingListener(in uint64_t windowId,
|
||||
void registerRespondingListener(in unsigned long long windowId,
|
||||
in nsIPresentationRespondingListener listener);
|
||||
|
||||
/*
|
||||
* Unregister a responding listener. Must be called from the main thread.
|
||||
* @param windowId: The window ID associated with the listener.
|
||||
*/
|
||||
void unregisterRespondingListener(in uint64_t windowId);
|
||||
void unregisterRespondingListener(in unsigned long long windowId);
|
||||
|
||||
/*
|
||||
* Check if the presentation instance has an existent session ID at launch.
|
||||
@@ -128,7 +136,7 @@ interface nsIPresentationService : nsISupports
|
||||
*
|
||||
* @param windowId: The inner window ID used to look up the session ID.
|
||||
*/
|
||||
DOMString getExistentSessionIdAtLaunch(in uint64_t windowId);
|
||||
DOMString getExistentSessionIdAtLaunch(in unsigned long long windowId);
|
||||
|
||||
/*
|
||||
* Notify the receiver page is ready for presentation use.
|
||||
@@ -136,10 +144,11 @@ interface nsIPresentationService : nsISupports
|
||||
* @param sessionId: An ID to identify presentation session.
|
||||
* @param windowId: The inner window ID associated with the presentation
|
||||
* session. (0 implies no window ID since no actual window
|
||||
* uses 0 as its ID.)
|
||||
* uses 0 as its ID. Generally it's the case the window is
|
||||
* located in different process from this service)
|
||||
*/
|
||||
void notifyReceiverReady(in DOMString sessionId,
|
||||
[optional] in uint64_t windowId);
|
||||
[optional] in unsigned long long windowId);
|
||||
|
||||
/*
|
||||
* Untrack the relevant info about the presentation session if there's any.
|
||||
@@ -147,4 +156,9 @@ interface nsIPresentationService : nsISupports
|
||||
* @param sessionId: An ID to identify presentation session.
|
||||
*/
|
||||
void untrackSessionInfo(in DOMString sessionId);
|
||||
|
||||
/*
|
||||
* The windowId for building RTCDataChannel session transport
|
||||
*/
|
||||
unsigned long long getWindowIdBySessionId(in DOMString sessionId);
|
||||
};
|
||||
|
||||
@@ -6,12 +6,10 @@
|
||||
|
||||
interface nsIInputStream;
|
||||
interface nsINetAddr;
|
||||
interface nsIPresentationChannelDescription;
|
||||
interface nsISocketTransport;
|
||||
|
||||
%{C++
|
||||
#define PRESENTATION_SESSION_TRANSPORT_CONTRACTID \
|
||||
"@mozilla.org/presentation/presentationsessiontransport;1"
|
||||
#define PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID \
|
||||
"@mozilla.org/presentation/presentationtcpsessiontransport;1"
|
||||
%}
|
||||
|
||||
/*
|
||||
@@ -28,32 +26,19 @@ interface nsIPresentationSessionTransportCallback : nsISupports
|
||||
/*
|
||||
* App-to-App transport channel for the presentation session.
|
||||
*/
|
||||
[scriptable, uuid(b6a416cf-03ae-4e74-9cda-88828e8ff418)]
|
||||
[scriptable, uuid(670b7e1b-65be-42b6-a596-be571907fa18)]
|
||||
interface nsIPresentationSessionTransport : nsISupports
|
||||
{
|
||||
// Should be set once the underlying session transport is built
|
||||
attribute nsIPresentationSessionTransportCallback callback;
|
||||
|
||||
// valid for TCP session transport
|
||||
readonly attribute nsINetAddr selfAddress;
|
||||
|
||||
/*
|
||||
* Initialize the transport channel with an existent socket transport. (This
|
||||
* is primarily used at the sender side.)
|
||||
* @param transport The socket transport.
|
||||
* @param callback The callback for followup notifications.
|
||||
*/
|
||||
void initWithSocketTransport(in nsISocketTransport transport,
|
||||
in nsIPresentationSessionTransportCallback callback);
|
||||
|
||||
/*
|
||||
* Initialize the transport channel with the channel description. (This is
|
||||
* primarily used at the receiver side.)
|
||||
* @param description The channel description.
|
||||
* @param callback The callback for followup notifications.
|
||||
*/
|
||||
void initWithChannelDescription(in nsIPresentationChannelDescription description,
|
||||
in nsIPresentationSessionTransportCallback callback);
|
||||
|
||||
/*
|
||||
* Enable the notification for incoming data. |notifyData| of
|
||||
* |nsIPresentationSessionTransportCallback| can start getting invoked.
|
||||
* Should set callback before |enableDataNotification| is called.
|
||||
*/
|
||||
void enableDataNotification();
|
||||
|
||||
@@ -61,7 +46,7 @@ interface nsIPresentationSessionTransport : nsISupports
|
||||
* Send message to the remote endpoint.
|
||||
* @param data The message to send.
|
||||
*/
|
||||
void send(in nsIInputStream data);
|
||||
void send(in DOMString data);
|
||||
|
||||
/*
|
||||
* Close this session transport.
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/* 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 nsIPresentationChannelDescription;
|
||||
interface nsISocketTransport;
|
||||
interface mozIDOMWindow;
|
||||
interface nsIPresentationControlChannel;
|
||||
interface nsIPresentationSessionTransport;
|
||||
|
||||
[scriptable, uuid(673f6de1-e253-41b8-9be8-b7ff161fa8dc)]
|
||||
interface nsIPresentationSessionTransportBuilderListener : nsISupports
|
||||
{
|
||||
// Should set |transport.callback| in |onSessionTransport|.
|
||||
void onSessionTransport(in nsIPresentationSessionTransport transport);
|
||||
|
||||
void onError(in nsresult reason);
|
||||
};
|
||||
|
||||
[scriptable, uuid(2fdbe67d-80f9-48dc-8237-5bef8fa19801)]
|
||||
interface nsIPresentationSessionTransportBuilder : nsISupports
|
||||
{
|
||||
const unsigned short TYPE_SENDER = 1;
|
||||
const unsigned short TYPE_RECEIVER = 2;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builder for TCP session transport
|
||||
*/
|
||||
[scriptable, uuid(cde36d6e-f471-4262-a70d-f932a26b21d9)]
|
||||
interface nsIPresentationTCPSessionTransportBuilder : nsIPresentationSessionTransportBuilder
|
||||
{
|
||||
/**
|
||||
* The following creation functions will trigger |listener.onSessionTransport|
|
||||
* if the session transport is successfully built, |listener.onError| if some
|
||||
* error occurs during building session transport.
|
||||
*/
|
||||
void buildTCPSenderTransport(in nsISocketTransport aTransport,
|
||||
in nsIPresentationSessionTransportBuilderListener aListener);
|
||||
|
||||
void buildTCPReceiverTransport(in nsIPresentationChannelDescription aDescription,
|
||||
in nsIPresentationSessionTransportBuilderListener aListener);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builder for WebRTC data channel session transport
|
||||
*/
|
||||
[scriptable, uuid(8131c4e0-3a8c-4bc1-a92a-8431473d2fe8)]
|
||||
interface nsIPresentationDataChannelSessionTransportBuilder : nsIPresentationSessionTransportBuilder
|
||||
{
|
||||
/**
|
||||
* The following creation function will trigger |listener.onSessionTransport|
|
||||
* if the session transport is successfully built, |listener.onError| if some
|
||||
* error occurs during creating session transport. The |notifyOpened| of
|
||||
* |aControlChannel| should be called before calling
|
||||
* |buildDataChannelTransport|.
|
||||
*/
|
||||
void buildDataChannelTransport(in uint8_t aType,
|
||||
in mozIDOMWindow aWindow,
|
||||
in nsIPresentationControlChannel aControlChannel,
|
||||
in nsIPresentationSessionTransportBuilderListener aListener);
|
||||
};
|
||||
@@ -17,12 +17,13 @@ struct StartSessionRequest
|
||||
nsString url;
|
||||
nsString sessionId;
|
||||
nsString origin;
|
||||
nsString deviceId;
|
||||
};
|
||||
|
||||
struct SendSessionMessageRequest
|
||||
{
|
||||
nsString sessionId;
|
||||
InputStreamParams data;
|
||||
nsString data;
|
||||
};
|
||||
|
||||
struct CloseSessionRequest
|
||||
|
||||
@@ -49,25 +49,30 @@ NS_IMETHODIMP
|
||||
PresentationIPCService::StartSession(const nsAString& aUrl,
|
||||
const nsAString& aSessionId,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aDeviceId,
|
||||
uint64_t aWindowId,
|
||||
nsIPresentationServiceCallback* aCallback)
|
||||
{
|
||||
return SendRequest(aCallback,
|
||||
StartSessionRequest(nsAutoString(aUrl), nsAutoString(aSessionId), nsAutoString(aOrigin)));
|
||||
if (aWindowId != 0) {
|
||||
mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
|
||||
mRespondingWindowIds.Put(aSessionId, aWindowId);
|
||||
}
|
||||
|
||||
return SendRequest(aCallback, StartSessionRequest(nsString(aUrl),
|
||||
nsString(aSessionId),
|
||||
nsString(aOrigin),
|
||||
nsString(aDeviceId)));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationIPCService::SendSessionMessage(const nsAString& aSessionId,
|
||||
nsIInputStream* aStream)
|
||||
const nsAString& aData)
|
||||
{
|
||||
MOZ_ASSERT(!aSessionId.IsEmpty());
|
||||
MOZ_ASSERT(aStream);
|
||||
MOZ_ASSERT(!aData.IsEmpty());
|
||||
|
||||
mozilla::ipc::OptionalInputStreamParams stream;
|
||||
nsTArray<mozilla::ipc::FileDescriptor> fds;
|
||||
SerializeInputStream(aStream, stream, fds);
|
||||
MOZ_ASSERT(fds.IsEmpty());
|
||||
|
||||
return SendRequest(nullptr, SendSessionMessageRequest(nsAutoString(aSessionId), stream));
|
||||
return SendRequest(nullptr, SendSessionMessageRequest(nsString(aSessionId),
|
||||
nsString(aData)));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -75,7 +80,7 @@ PresentationIPCService::CloseSession(const nsAString& aSessionId)
|
||||
{
|
||||
MOZ_ASSERT(!aSessionId.IsEmpty());
|
||||
|
||||
return SendRequest(nullptr, CloseSessionRequest(nsAutoString(aSessionId)));
|
||||
return SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId)));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -83,7 +88,7 @@ PresentationIPCService::TerminateSession(const nsAString& aSessionId)
|
||||
{
|
||||
MOZ_ASSERT(!aSessionId.IsEmpty());
|
||||
|
||||
return SendRequest(nullptr, TerminateSessionRequest(nsAutoString(aSessionId)));
|
||||
return SendRequest(nullptr, TerminateSessionRequest(nsString(aSessionId)));
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -132,7 +137,7 @@ PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId,
|
||||
|
||||
mSessionListeners.Put(aSessionId, aListener);
|
||||
if (sPresentationChild) {
|
||||
NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsAutoString(aSessionId)));
|
||||
NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsString(aSessionId)));
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
@@ -146,7 +151,7 @@ PresentationIPCService::UnregisterSessionListener(const nsAString& aSessionId)
|
||||
|
||||
mSessionListeners.Remove(aSessionId);
|
||||
if (sPresentationChild) {
|
||||
NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsAutoString(aSessionId)));
|
||||
NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsString(aSessionId)));
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
@@ -176,6 +181,16 @@ PresentationIPCService::UnregisterRespondingListener(uint64_t aWindowId)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId,
|
||||
uint64_t* aWindowId)
|
||||
{
|
||||
if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
|
||||
return NS_OK;
|
||||
}
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
nsresult
|
||||
PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId,
|
||||
uint16_t aState)
|
||||
@@ -251,11 +266,14 @@ PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId,
|
||||
}
|
||||
|
||||
// Track the responding info for an OOP receiver page.
|
||||
mRespondingSessionIds.Put(aWindowId, new nsAutoString(aSessionId));
|
||||
mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
|
||||
mRespondingWindowIds.Put(aSessionId, aWindowId);
|
||||
|
||||
NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsString(aSessionId)));
|
||||
|
||||
// Release mCallback after using aSessionId
|
||||
// because aSessionId is held by mCallback.
|
||||
mCallback = nullptr;
|
||||
NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsAutoString(aSessionId)));
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -264,7 +282,7 @@ PresentationIPCService::UntrackSessionInfo(const nsAString& aSessionId)
|
||||
{
|
||||
// Remove the OOP responding info (if it has never been used).
|
||||
uint64_t windowId = 0;
|
||||
if(mRespondingWindowIds.Get(aSessionId, &windowId)) {
|
||||
if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
|
||||
mRespondingWindowIds.Remove(aSessionId);
|
||||
mRespondingSessionIds.Remove(windowId);
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ PresentationParent::NotifyStateChange(const nsAString& aSessionId,
|
||||
uint16_t aState)
|
||||
{
|
||||
if (NS_WARN_IF(mActorDestroyed ||
|
||||
!SendNotifySessionStateChange(nsAutoString(aSessionId), aState))) {
|
||||
!SendNotifySessionStateChange(nsString(aSessionId), aState))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
@@ -197,7 +197,7 @@ PresentationParent::NotifyMessage(const nsAString& aSessionId,
|
||||
const nsACString& aData)
|
||||
{
|
||||
if (NS_WARN_IF(mActorDestroyed ||
|
||||
!SendNotifyMessage(nsAutoString(aSessionId), nsAutoCString(aData)))) {
|
||||
!SendNotifyMessage(nsString(aSessionId), nsCString(aData)))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
@@ -208,7 +208,7 @@ PresentationParent::NotifySessionConnect(uint64_t aWindowId,
|
||||
const nsAString& aSessionId)
|
||||
{
|
||||
if (NS_WARN_IF(mActorDestroyed ||
|
||||
!SendNotifySessionConnect(aWindowId, nsAutoString(aSessionId)))) {
|
||||
!SendNotifySessionConnect(aWindowId, nsString(aSessionId)))) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
return NS_OK;
|
||||
@@ -218,6 +218,8 @@ bool
|
||||
PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId)
|
||||
{
|
||||
MOZ_ASSERT(mService);
|
||||
|
||||
// Set window ID to 0 since the window is from content process.
|
||||
NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, 0)));
|
||||
return true;
|
||||
}
|
||||
@@ -251,8 +253,10 @@ nsresult
|
||||
PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
|
||||
{
|
||||
MOZ_ASSERT(mService);
|
||||
|
||||
// Set window ID to 0 since the window is from content process.
|
||||
return mService->StartSession(aRequest.url(), aRequest.sessionId(),
|
||||
aRequest.origin(), this);
|
||||
aRequest.origin(), aRequest.deviceId(), 0, this);
|
||||
}
|
||||
|
||||
nsresult
|
||||
@@ -267,13 +271,8 @@ PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
|
||||
return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
|
||||
}
|
||||
|
||||
nsTArray<mozilla::ipc::FileDescriptor> fds;
|
||||
nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aRequest.data(), fds);
|
||||
if(NS_WARN_IF(!stream)) {
|
||||
return NotifyError(NS_ERROR_NOT_AVAILABLE);
|
||||
}
|
||||
|
||||
nsresult rv = mService->SendSessionMessage(aRequest.sessionId(), stream);
|
||||
nsresult rv = mService->SendSessionMessage(aRequest.sessionId(),
|
||||
aRequest.data());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return NotifyError(rv);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ DIRS += ['interfaces', 'provider']
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
|
||||
MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'ipc/PresentationChild.h',
|
||||
@@ -22,7 +23,7 @@ EXPORTS.mozilla.dom += [
|
||||
'PresentationRequest.h',
|
||||
'PresentationService.h',
|
||||
'PresentationSessionInfo.h',
|
||||
'PresentationSessionTransport.h',
|
||||
'PresentationTCPSessionTransport.h',
|
||||
]
|
||||
|
||||
UNIFIED_SOURCES += [
|
||||
@@ -39,10 +40,12 @@ UNIFIED_SOURCES += [
|
||||
'PresentationService.cpp',
|
||||
'PresentationSessionInfo.cpp',
|
||||
'PresentationSessionRequest.cpp',
|
||||
'PresentationSessionTransport.cpp',
|
||||
'PresentationTCPSessionTransport.cpp',
|
||||
]
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'PresentationDataChannelSessionTransport.js',
|
||||
'PresentationDataChannelSessionTransport.manifest',
|
||||
'PresentationDeviceInfoManager.js',
|
||||
'PresentationDeviceInfoManager.manifest',
|
||||
]
|
||||
@@ -62,6 +65,10 @@ IPDL_SOURCES += [
|
||||
'ipc/PPresentationRequest.ipdl'
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'../base'
|
||||
]
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
@@ -489,7 +489,7 @@ TCPControlChannel.prototype = {
|
||||
onStopRequest: function(aRequest, aContext, aStatus) {
|
||||
DEBUG && log("TCPControlChannel - onStopRequest: " + aStatus
|
||||
+ " with role: " + this._direction);
|
||||
this.close(Cr.NS_OK);
|
||||
this.close(aStatus);
|
||||
this._notifyClosed(aStatus);
|
||||
},
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
Cu.import('resource://gre/modules/Services.jsm');
|
||||
Cu.import('resource://gre/modules/Timer.jsm');
|
||||
|
||||
function registerMockedFactory(contractId, mockedClassId, mockedFactory) {
|
||||
var originalClassId, originalFactory;
|
||||
@@ -49,7 +51,12 @@ addresses.appendElement(address, false);
|
||||
|
||||
const mockedChannelDescription = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
|
||||
type: 1,
|
||||
get type() {
|
||||
if (Services.prefs.getBoolPref("dom.presentation.session_transport.data_channel.enable")) {
|
||||
return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
|
||||
}
|
||||
return Ci.nsIPresentationChannelDescription.TYPE_TCP;
|
||||
},
|
||||
tcpAddress: addresses,
|
||||
tcpPort: 1234,
|
||||
};
|
||||
@@ -198,6 +205,9 @@ const mockedDevicePrompt = {
|
||||
|
||||
const mockedSessionTransport = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport,
|
||||
Ci.nsIPresentationTCPSessionTransportBuilder,
|
||||
Ci.nsIPresentationDataChannelSessionTransportBuilder,
|
||||
Ci.nsIPresentationControlChannelListener,
|
||||
Ci.nsIFactory]),
|
||||
createInstance: function(aOuter, aIID) {
|
||||
if (aOuter) {
|
||||
@@ -214,13 +224,20 @@ const mockedSessionTransport = {
|
||||
get selfAddress() {
|
||||
return this._selfAddress;
|
||||
},
|
||||
initWithSocketTransport: function(transport, callback) {
|
||||
buildTCPSenderTransport: function(transport, listener) {
|
||||
sendAsyncMessage('data-transport-initialized');
|
||||
this._callback = callback;
|
||||
this.simulateTransportReady();
|
||||
this._listener = listener;
|
||||
this._type = Ci.nsIPresentationSessionTransportBuilder.TYPE_SENDER;
|
||||
|
||||
setTimeout(()=>{
|
||||
this._listener.onSessionTransport(this);
|
||||
this._listener = null;
|
||||
this.simulateTransportReady();
|
||||
}, 0);
|
||||
},
|
||||
initWithChannelDescription: function(description, callback) {
|
||||
this._callback = callback;
|
||||
buildTCPReceiverTransport: function(description, listener) {
|
||||
this._listener = listener;
|
||||
this._type = Ci.nsIPresentationSessionTransportBuilder.TYPE_RECEIVER;
|
||||
|
||||
var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress;
|
||||
this._selfAddress = {
|
||||
@@ -229,16 +246,32 @@ const mockedSessionTransport = {
|
||||
addresses.queryElementAt(0, Ci.nsISupportsCString).data : "",
|
||||
port: description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpPort,
|
||||
};
|
||||
|
||||
setTimeout(()=>{
|
||||
this._listener.onSessionTransport(this);
|
||||
this._listener = null;
|
||||
}, 0);
|
||||
},
|
||||
// in-process case
|
||||
buildDataChannelTransport: function(type, window, controlChannel, listener) {
|
||||
dump("build data channel transport\n");
|
||||
this._listener = listener;
|
||||
this._type = type;
|
||||
|
||||
var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
|
||||
sendAsyncMessage('check-navigator', hasNavigator);
|
||||
|
||||
setTimeout(()=>{
|
||||
this._listener.onSessionTransport(this);
|
||||
this._listener = null;
|
||||
this.simulateTransportReady();
|
||||
}, 0);
|
||||
},
|
||||
enableDataNotification: function() {
|
||||
sendAsyncMessage('data-transport-notification-enabled');
|
||||
},
|
||||
send: function(data) {
|
||||
var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
|
||||
createInstance(Ci.nsIBinaryInputStream);
|
||||
binaryStream.setInputStream(data);
|
||||
var message = binaryStream.readBytes(binaryStream.available());
|
||||
sendAsyncMessage('message-sent', message);
|
||||
sendAsyncMessage('message-sent', data);
|
||||
},
|
||||
close: function(reason) {
|
||||
sendAsyncMessage('data-transport-closed', reason);
|
||||
@@ -250,6 +283,10 @@ const mockedSessionTransport = {
|
||||
simulateIncomingMessage: function(message) {
|
||||
this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message);
|
||||
},
|
||||
onOffer: function(aOffer) {
|
||||
},
|
||||
onAnswer: function(aAnswer) {
|
||||
}
|
||||
};
|
||||
|
||||
const mockedNetworkInfo = {
|
||||
@@ -302,7 +339,10 @@ originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation-device
|
||||
originalFactoryData.push(registerMockedFactory("@mozilla.org/network/server-socket;1",
|
||||
uuidGenerator.generateUUID(),
|
||||
mockedServerSocket));
|
||||
originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/presentationsessiontransport;1",
|
||||
originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/presentationtcpsessiontransport;1",
|
||||
uuidGenerator.generateUUID(),
|
||||
mockedSessionTransport));
|
||||
originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/datachanneltransportbuilder;1",
|
||||
uuidGenerator.generateUUID(),
|
||||
mockedSessionTransport));
|
||||
originalFactoryData.push(registerMockedFactory("@mozilla.org/network/manager;1",
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
[DEFAULT]
|
||||
skip-if = buildapp == 'b2g' || os == 'android'
|
||||
|
||||
[test_presentation_datachannel_sessiontransport.html]
|
||||
@@ -77,7 +77,7 @@ function testIncomingMessage() {
|
||||
});
|
||||
|
||||
command({ name: 'trigger-incoming-message',
|
||||
data: incomingMessage });
|
||||
data: incomingMessage });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,21 +7,28 @@ support-files =
|
||||
file_presentation_non_receiver_oop.html
|
||||
file_presentation_receiver_establish_connection_error.html
|
||||
|
||||
|
||||
[test_presentation_dc_sender.html]
|
||||
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
|
||||
[test_presentation_dc_receiver.html]
|
||||
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
|
||||
[test_presentation_device_info.html]
|
||||
[test_presentation_device_info_permission.html]
|
||||
[test_presentation_sender_disconnect.html]
|
||||
[test_presentation_sender_startWithDevice.html]
|
||||
skip-if = toolkit == 'android' # Bug 1129785
|
||||
[test_presentation_sender_establish_connection_error.html]
|
||||
[test_presentation_tcp_sender_disconnect.html]
|
||||
skip-if = toolkit == 'android' # Bug 1129785
|
||||
[test_presentation_sender.html]
|
||||
[test_presentation_tcp_sender_establish_connection_error.html]
|
||||
skip-if = toolkit == 'android' # Bug 1129785
|
||||
[test_presentation_sender_default_request.html]
|
||||
[test_presentation_tcp_sender.html]
|
||||
skip-if = toolkit == 'android' # Bug 1129785
|
||||
[test_presentation_receiver_establish_connection_error.html]
|
||||
[test_presentation_tcp_sender_default_request.html]
|
||||
skip-if = toolkit == 'android' # Bug 1129785
|
||||
[test_presentation_tcp_receiver_establish_connection_error.html]
|
||||
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android' || os == 'mac' || os == 'win' || buildapp == 'mulet') # Bug 1129785, Bug 1204709
|
||||
[test_presentation_receiver_establish_connection_timeout.html]
|
||||
[test_presentation_tcp_receiver_establish_connection_timeout.html]
|
||||
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
|
||||
[test_presentation_receiver.html]
|
||||
[test_presentation_tcp_receiver.html]
|
||||
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
|
||||
[test_presentation_receiver_oop.html]
|
||||
[test_presentation_tcp_receiver_oop.html]
|
||||
skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for data channel as session transport in Presentation API</title>
|
||||
<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for data channel as session transport in Presentation API</a>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
|
||||
const loadingTimeoutPref = "presentation.receiver.loading.timeout";
|
||||
|
||||
var clientBuilder;
|
||||
var serverBuilder;
|
||||
var clientTransport;
|
||||
var serverTransport;
|
||||
var clientControlChannel;
|
||||
var serverControlChannel;
|
||||
|
||||
const clientMessage = "Client Message";
|
||||
const serverMessage = "Server Message";
|
||||
|
||||
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
const { Services } = Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function TestControlChannel() {
|
||||
this._listener = null;
|
||||
}
|
||||
|
||||
TestControlChannel.prototype = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
|
||||
set listener(aListener) {
|
||||
this._listener = aListener;
|
||||
},
|
||||
get listener() {
|
||||
return this._listener;
|
||||
},
|
||||
sendOffer: function(aOffer) {
|
||||
setTimeout(()=>this._remote.listener.onOffer(aOffer), 0);
|
||||
},
|
||||
sendAnswer: function(aAnswer) {
|
||||
setTimeout(()=>this._remote.listener.onAnswer(aAnswer), 0);
|
||||
},
|
||||
sendIceCandidate: function(aCandidate) {
|
||||
setTimeout(()=>this._remote.listener.onIceCandidate(aCandidate), 0);
|
||||
},
|
||||
close: function(aReason) {
|
||||
setTimeout(()=>this._listener.notifyClosed(aReason), 0);
|
||||
setTimeout(()=>this._remote.listener.notifyClosed(aReason), 0);
|
||||
},
|
||||
set remote(aRemote) {
|
||||
this._remote = aRemote;
|
||||
},
|
||||
};
|
||||
|
||||
var isClientReady = false;
|
||||
var isServerReady = false;
|
||||
var isClientClosed = false;
|
||||
var isServerClosed = false;
|
||||
|
||||
var gResolve;
|
||||
var gReject;
|
||||
|
||||
const clientCallback = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
|
||||
notifyTransportReady: function () {
|
||||
info("Client transport ready.");
|
||||
|
||||
isClientReady = true;
|
||||
if (isClientReady && isServerReady) {
|
||||
gResolve();
|
||||
}
|
||||
},
|
||||
notifyTransportClosed: function (aReason) {
|
||||
info("Client transport is closed.");
|
||||
|
||||
isClientClosed = true;
|
||||
if (isClientClosed && isServerClosed) {
|
||||
gResolve();
|
||||
}
|
||||
},
|
||||
notifyData: function(aData) {
|
||||
is(aData, serverMessage, "Client transport receives data.");
|
||||
gResolve();
|
||||
},
|
||||
};
|
||||
|
||||
const serverCallback = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]),
|
||||
notifyTransportReady: function () {
|
||||
info("Server transport ready.");
|
||||
|
||||
isServerReady = true;
|
||||
if (isClientReady && isServerReady) {
|
||||
gResolve();
|
||||
}
|
||||
},
|
||||
notifyTransportClosed: function (aReason) {
|
||||
info("Server transport is closed.");
|
||||
|
||||
isServerClosed = true;
|
||||
if (isClientClosed && isServerClosed) {
|
||||
gResolve();
|
||||
}
|
||||
},
|
||||
notifyData: function(aData) {
|
||||
is(aData, clientMessage, "Server transport receives data.");
|
||||
gResolve()
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
const clientListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
|
||||
onSessionTransport: function(aTransport) {
|
||||
info("Client Transport is built.");
|
||||
clientTransport = aTransport;
|
||||
clientTransport.callback = clientCallback;
|
||||
},
|
||||
onError: function(aError) {
|
||||
ok(false, "client's builder reports error " + aError);
|
||||
}
|
||||
}
|
||||
|
||||
const serverListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
|
||||
onSessionTransport: function(aTransport) {
|
||||
info("Server Transport is built.");
|
||||
serverTransport = aTransport;
|
||||
serverTransport.callback = serverCallback;
|
||||
serverTransport.enableDataNotification();
|
||||
},
|
||||
onError: function(aError) {
|
||||
ok(false, "server's builder reports error " + aError);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function testBuilder() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gResolve = aResolve;
|
||||
gReject = aReject;
|
||||
|
||||
clientControlChannel = new TestControlChannel();
|
||||
serverControlChannel = new TestControlChannel();
|
||||
clientControlChannel.remote = serverControlChannel;
|
||||
serverControlChannel.remote = clientControlChannel;
|
||||
|
||||
clientBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"]
|
||||
.createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder);
|
||||
serverBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"]
|
||||
.createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder);
|
||||
|
||||
clientBuilder
|
||||
.buildDataChannelTransport(Ci.nsIPresentationSessionTransportBuilder.TYPE_SENDER,
|
||||
window,
|
||||
clientControlChannel,
|
||||
clientListener);
|
||||
|
||||
serverBuilder
|
||||
.buildDataChannelTransport(Ci.nsIPresentationSessionTransportBuilder.TYPE_RECEIVER,
|
||||
window,
|
||||
serverControlChannel,
|
||||
serverListener);
|
||||
});
|
||||
}
|
||||
|
||||
function testClientSendMessage() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
info("client sends message");
|
||||
gResolve = aResolve;
|
||||
gReject = aReject;
|
||||
|
||||
clientTransport.send(clientMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function testServerSendMessage() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
info("server sends message");
|
||||
gResolve = aResolve;
|
||||
gReject = aReject;
|
||||
|
||||
serverTransport.send(serverMessage);
|
||||
setTimeout(()=>clientTransport.enableDataNotification(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
function testCloseSessionTransport() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
info("close session transport");
|
||||
gResolve = aResolve;
|
||||
gReject = aReject;
|
||||
|
||||
serverTransport.close(Cr.NS_OK);
|
||||
});
|
||||
}
|
||||
|
||||
function finish() {
|
||||
info("test finished, teardown");
|
||||
Services.prefs.clearUserPref(loadingTimeoutPref);
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
function error(aError) {
|
||||
ok(false, "report Error " + aError.name + ":" + aError.message);
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
Services.prefs.setIntPref(loadingTimeoutPref, 30000);
|
||||
|
||||
testBuilder()
|
||||
.then(testClientSendMessage)
|
||||
.then(testServerSendMessage)
|
||||
.then(testCloseSessionTransport)
|
||||
.then(finish)
|
||||
.catch(error);
|
||||
|
||||
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
runTests();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,136 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for B2G PresentationConnection API at receiver side</title>
|
||||
<script type="application/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=1148307">Test for B2G PresentationConnection API at receiver side</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none"></div>
|
||||
<pre id="test"></pre>
|
||||
<script type="application/javascript">
|
||||
|
||||
'use strict';
|
||||
|
||||
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
|
||||
var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
|
||||
|
||||
var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(SpecialPowers.Ci.nsIObserverService);
|
||||
|
||||
function setup() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.sendAsyncMessage('trigger-device-add');
|
||||
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('src', receiverUrl);
|
||||
|
||||
// This event is triggered when the iframe calls "postMessage".
|
||||
window.addEventListener('message', function listener(aEvent) {
|
||||
var message = aEvent.data;
|
||||
if (/^OK /.exec(message)) {
|
||||
ok(true, "Message from iframe: " + message);
|
||||
} else if (/^KO /.exec(message)) {
|
||||
ok(false, "Message from iframe: " + message);
|
||||
} else if (/^INFO /.exec(message)) {
|
||||
info("Message from iframe: " + message);
|
||||
} else if (/^COMMAND /.exec(message)) {
|
||||
var command = JSON.parse(message.replace(/^COMMAND /, ''));
|
||||
gScript.sendAsyncMessage(command.name, command.data);
|
||||
} else if (/^DONE$/.exec(message)) {
|
||||
ok(true, "Messaging from iframe complete.");
|
||||
window.removeEventListener('message', listener);
|
||||
|
||||
teardown();
|
||||
}
|
||||
}, false);
|
||||
|
||||
var promise = new Promise(function(aResolve, aReject) {
|
||||
document.body.appendChild(iframe);
|
||||
|
||||
aResolve(iframe);
|
||||
});
|
||||
obs.notifyObservers(promise, 'setup-request-promise', null);
|
||||
|
||||
gScript.addMessageListener('offer-received', function offerReceivedHandler() {
|
||||
gScript.removeMessageListener('offer-received', offerReceivedHandler);
|
||||
info("An offer is received.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('answer-sent', answerSentHandler);
|
||||
ok(aIsValid, "A valid answer is sent.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
|
||||
gScript.removeMessageListener('check-navigator', checknavigatorHandler);
|
||||
ok(aSuccess, "buildDataChannel get correct window object");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
|
||||
gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
|
||||
info("Data notification is enabled for data transport channel.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
is(aReason, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
|
||||
});
|
||||
|
||||
aResolve();
|
||||
});
|
||||
}
|
||||
|
||||
function testIncomingSessionRequest() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
|
||||
gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
|
||||
info("Trying to launch receiver page.");
|
||||
|
||||
ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
|
||||
ok(!navigator.presentation.receiver, "Non-receiving in-process pages shouldn't get a presentation receiver instance.");
|
||||
aResolve();
|
||||
});
|
||||
|
||||
gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
|
||||
});
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
|
||||
gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
|
||||
gScript.destroy();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
gScript.sendAsyncMessage('teardown');
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
setup().
|
||||
then(testIncomingSessionRequest);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.session_transport.data_channel.enable", true]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,208 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test for B2G Presentation API at sender side</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for B2G Presentation API at sender side</a>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
'use strict';
|
||||
|
||||
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
|
||||
var request;
|
||||
var connection;
|
||||
|
||||
function testSetup() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
request = new PresentationRequest("http://example.com");
|
||||
|
||||
request.getAvailability().then(
|
||||
function(aAvailability) {
|
||||
aAvailability.onchange = function() {
|
||||
aAvailability.onchange = null;
|
||||
ok(aAvailability.value, "Device should be available.");
|
||||
aResolve();
|
||||
}
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when getting availability: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
|
||||
gScript.sendAsyncMessage('trigger-device-add');
|
||||
});
|
||||
}
|
||||
|
||||
function testStartConnection() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
info("Device prompt is triggered.");
|
||||
gScript.sendAsyncMessage('trigger-device-prompt-select');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
|
||||
gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
|
||||
gScript.removeMessageListener('check-navigator', checknavigatorHandler);
|
||||
ok(aSuccess, "buildDataChannel get correct window object");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('answer-received', function answerReceivedHandler() {
|
||||
gScript.removeMessageListener('answer-received', answerReceivedHandler);
|
||||
info("An answer is received.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-answer');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
|
||||
gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
|
||||
info("Data notification is enabled for data transport channel.");
|
||||
});
|
||||
|
||||
var connectionFromEvent;
|
||||
request.onconnectionavailable = function(aEvent) {
|
||||
request.onconnectionavailable = null;
|
||||
connectionFromEvent = aEvent.connection;
|
||||
ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
|
||||
|
||||
if (connection) {
|
||||
is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
|
||||
aResolve();
|
||||
}
|
||||
};
|
||||
|
||||
request.start().then(
|
||||
function(aConnection) {
|
||||
connection = aConnection;
|
||||
ok(connection, "Connection should be available.");
|
||||
ok(connection.id, "Connection ID should be set.");
|
||||
is(connection.state, "connected", "Connection state at sender side should be connected by default.");
|
||||
|
||||
if (connectionFromEvent) {
|
||||
is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
|
||||
aResolve();
|
||||
}
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function testSend() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
const outgoingMessage = "test outgoing message";
|
||||
|
||||
gScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
|
||||
gScript.removeMessageListener('message-sent', messageSentHandler);
|
||||
is(aMessage, outgoingMessage, "The message is sent out.");
|
||||
aResolve();
|
||||
});
|
||||
|
||||
connection.send(outgoingMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function testIncomingMessage() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
const incomingMessage = "test incoming message";
|
||||
|
||||
connection.addEventListener('message', function messageHandler(aEvent) {
|
||||
connection.removeEventListener('message', messageHandler);
|
||||
is(aEvent.data, incomingMessage, "An incoming message should be received.");
|
||||
aResolve();
|
||||
});
|
||||
|
||||
gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
|
||||
});
|
||||
}
|
||||
|
||||
function testTerminateConnection() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
|
||||
info("The data transport is closed. " + aReason);
|
||||
});
|
||||
|
||||
connection.onstatechange = function() {
|
||||
connection.onstatechange = null;
|
||||
is(connection.state, "terminated", "Connection should be terminated.");
|
||||
aResolve();
|
||||
};
|
||||
|
||||
connection.terminate();
|
||||
});
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
|
||||
gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
|
||||
gScript.destroy();
|
||||
info('teardown-complete');
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
gScript.sendAsyncMessage('teardown');
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
ok(window.PresentationRequest, "PresentationRequest should be available.");
|
||||
|
||||
testSetup().
|
||||
then(testStartConnection).
|
||||
then(testSend).
|
||||
then(testIncomingMessage).
|
||||
then(testTerminateConnection).
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.session_transport.data_channel.enable", true]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,172 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Test startWithDevice for B2G Presentation API at sender side</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1239242">Test startWithDevice for B2G Presentation API at sender side</a>
|
||||
<script type="application/javascript;version=1.8">
|
||||
|
||||
'use strict';
|
||||
|
||||
var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
|
||||
var request;
|
||||
var connection;
|
||||
|
||||
function testSetup() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
request = new PresentationRequest("http://example.com");
|
||||
|
||||
request.getAvailability().then(
|
||||
function(aAvailability) {
|
||||
aAvailability.onchange = function() {
|
||||
aAvailability.onchange = null;
|
||||
ok(aAvailability.value, "Device should be available.");
|
||||
aResolve();
|
||||
}
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when getting availability: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
|
||||
gScript.sendAsyncMessage('trigger-device-add');
|
||||
});
|
||||
}
|
||||
|
||||
function testStartConnectionWithDevice() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
gScript.addMessageListener('device-prompt', function devicePromptHandler() {
|
||||
gScript.removeMessageListener('device-prompt', devicePromptHandler);
|
||||
ok(false, "Device prompt should not be triggered.");
|
||||
teardown();
|
||||
aReject();
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
|
||||
gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
|
||||
info("A control channel is established.");
|
||||
gScript.sendAsyncMessage('trigger-control-channel-open');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
|
||||
info("The control channel is opened.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
|
||||
gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
|
||||
info("The control channel is closed. " + aReason);
|
||||
});
|
||||
|
||||
gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
|
||||
gScript.removeMessageListener('offer-sent', offerSentHandler);
|
||||
ok(aIsValid, "A valid offer is sent out.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-transport');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('answer-received', function answerReceivedHandler() {
|
||||
gScript.removeMessageListener('answer-received', answerReceivedHandler);
|
||||
info("An answer is received.");
|
||||
});
|
||||
|
||||
gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
|
||||
gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
|
||||
info("Data transport channel is initialized.");
|
||||
gScript.sendAsyncMessage('trigger-incoming-answer');
|
||||
});
|
||||
|
||||
gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
|
||||
gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
|
||||
info("Data notification is enabled for data transport channel.");
|
||||
});
|
||||
|
||||
var connectionFromEvent;
|
||||
request.onconnectionavailable = function(aEvent) {
|
||||
request.onconnectionavailable = null;
|
||||
connectionFromEvent = aEvent.connection;
|
||||
ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
|
||||
|
||||
if (connection) {
|
||||
is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
|
||||
aResolve();
|
||||
}
|
||||
};
|
||||
|
||||
request.startWithDevice('id').then(
|
||||
function(aConnection) {
|
||||
connection = aConnection;
|
||||
ok(connection, "Connection should be available.");
|
||||
ok(connection.id, "Connection ID should be set.");
|
||||
is(connection.state, "connected", "Connection state at sender side should be connected by default.");
|
||||
|
||||
if (connectionFromEvent) {
|
||||
is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
|
||||
aResolve();
|
||||
}
|
||||
},
|
||||
function(aError) {
|
||||
ok(false, "Error occurred when establishing a connection: " + aError);
|
||||
teardown();
|
||||
aReject();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function testStartConnectionWithDeviceNotFoundError() {
|
||||
return new Promise(function(aResolve, aReject) {
|
||||
request.startWithDevice('').then(
|
||||
function(aConnection) {
|
||||
ok(false, "Should not establish connection to an unknown device");
|
||||
teardown();
|
||||
aReject();
|
||||
},
|
||||
function(aError) {
|
||||
is(aError.name, 'NotFoundError', "Expect NotFoundError occurred when establishing a connection");
|
||||
aResolve();
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function teardown() {
|
||||
gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
|
||||
gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
|
||||
gScript.destroy();
|
||||
SimpleTest.finish();
|
||||
});
|
||||
|
||||
gScript.sendAsyncMessage('teardown');
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
ok(window.PresentationRequest, "PresentationRequest should be available.");
|
||||
|
||||
testSetup().
|
||||
then(testStartConnectionWithDevice).
|
||||
then(testStartConnectionWithDeviceNotFoundError).
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: true, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-3
@@ -116,15 +116,13 @@ function runTests() {
|
||||
then(testIncomingSessionRequest);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
["dom.presentation.session_transport.data_channel.enable", false]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
+1
-3
@@ -91,15 +91,13 @@ function runTests() {
|
||||
then(testIncomingSessionRequest);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
["dom.presentation.session_transport.data_channel.enable", false]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
+1
-3
@@ -65,15 +65,13 @@ function runTests() {
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0],
|
||||
["dom.presentation.session_transport.data_channel.enable", false],
|
||||
["presentation.receiver.loading.timeout", 10]]},
|
||||
runTests);
|
||||
});
|
||||
+1
-3
@@ -157,7 +157,6 @@ function runTests() {
|
||||
then(testIncomingSessionRequest);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
@@ -165,8 +164,7 @@ SpecialPowers.pushPermissions([
|
||||
{type: 'browser', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0],
|
||||
["dom.presentation.session_transport.data_channel.enable", false],
|
||||
["dom.mozBrowserFramesEnabled", true],
|
||||
["dom.ipc.browser_frames.oop_by_default", true]]},
|
||||
runTests);
|
||||
+1
-3
@@ -186,15 +186,13 @@ function runTests() {
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
["dom.presentation.session_transport.data_channel.enable", false]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
+1
-3
@@ -134,15 +134,13 @@ function runTests() {
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
["dom.presentation.session_transport.data_channel.enable", false]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
+1
-3
@@ -140,15 +140,13 @@ function runTests() {
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
["dom.presentation.session_transport.data_channel.enable", false]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
+1
-3
@@ -344,15 +344,13 @@ function runTests() {
|
||||
then(teardown);
|
||||
}
|
||||
|
||||
SimpleTest.expectAssertions(0, 5);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPermissions([
|
||||
{type: 'presentation-device-manage', allow: false, context: document},
|
||||
{type: 'presentation', allow: true, context: document},
|
||||
], function() {
|
||||
SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
|
||||
["dom.presentation.test.enabled", true],
|
||||
["dom.presentation.test.stage", 0]]},
|
||||
["dom.presentation.session_transport.data_channel.enable", false]]},
|
||||
runTests);
|
||||
});
|
||||
|
||||
@@ -16,6 +16,9 @@ var testServer = null;
|
||||
var clientTransport = null;
|
||||
var serverTransport = null;
|
||||
|
||||
var clientBuilder = null;
|
||||
var serverBuilder = null;
|
||||
|
||||
const clientMessage = "Client Message";
|
||||
const serverMessage = "Server Message";
|
||||
|
||||
@@ -84,6 +87,33 @@ const serverCallback = {
|
||||
},
|
||||
};
|
||||
|
||||
const clientListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
|
||||
onSessionTransport(aTransport) {
|
||||
Assert.ok(true, "Client Transport is built.");
|
||||
clientTransport = aTransport;
|
||||
clientTransport.callback = clientCallback;
|
||||
|
||||
if (serverTransport) {
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const serverListener = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
|
||||
onSessionTransport(aTransport) {
|
||||
Assert.ok(true, "Server Transport is built.");
|
||||
serverTransport = aTransport;
|
||||
serverTransport.callback = serverCallback;
|
||||
serverTransport.enableDataNotification();
|
||||
|
||||
if (clientTransport) {
|
||||
run_next_test();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function TestServer() {
|
||||
this.serverSocket = ServerSocket(-1, true, -1);
|
||||
this.serverSocket.asyncListen(this)
|
||||
@@ -92,10 +122,9 @@ function TestServer() {
|
||||
TestServer.prototype = {
|
||||
onSocketAccepted: function(aSocket, aTransport) {
|
||||
print("Test server gets a client connection.");
|
||||
serverTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"]
|
||||
.createInstance(Ci.nsIPresentationSessionTransport);
|
||||
serverTransport.initWithSocketTransport(aTransport, serverCallback);
|
||||
serverTransport.enableDataNotification();
|
||||
serverBuilder = Cc["@mozilla.org/presentation/presentationtcpsessiontransport;1"]
|
||||
.createInstance(Ci.nsIPresentationTCPSessionTransportBuilder);
|
||||
serverBuilder.buildTCPSenderTransport(aTransport, serverListener);
|
||||
},
|
||||
onStopListening: function(aSocket) {
|
||||
print("Test server stops listening.");
|
||||
@@ -111,9 +140,9 @@ TestServer.prototype = {
|
||||
// Set up the transport connection and ensure |notifyTransportReady| triggered
|
||||
// at both sides.
|
||||
function setup() {
|
||||
clientTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"]
|
||||
.createInstance(Ci.nsIPresentationSessionTransport);
|
||||
clientTransport.initWithChannelDescription(serverChannelDescription, clientCallback);
|
||||
clientBuilder = Cc["@mozilla.org/presentation/presentationtcpsessiontransport;1"]
|
||||
.createInstance(Ci.nsIPresentationTCPSessionTransportBuilder);
|
||||
clientBuilder.buildTCPReceiverTransport(serverChannelDescription, clientListener);
|
||||
}
|
||||
|
||||
// Test |selfAddress| attribute of |nsIPresentationSessionTransport|.
|
||||
@@ -132,19 +161,13 @@ function selfAddress() {
|
||||
// Test the client sends a message and then a corresponding notification gets
|
||||
// triggered at the server side.
|
||||
function clientSendMessage() {
|
||||
var stream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stream.setData(clientMessage, clientMessage.length);
|
||||
clientTransport.send(stream);
|
||||
clientTransport.send(clientMessage);
|
||||
}
|
||||
|
||||
// Test the server sends a message an then a corresponding notification gets
|
||||
// triggered at the client side.
|
||||
function serverSendMessage() {
|
||||
var stream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stream.setData(serverMessage, serverMessage.length);
|
||||
serverTransport.send(stream);
|
||||
serverTransport.send(serverMessage);
|
||||
// The client enables data notification even after the incoming message has
|
||||
// been sent, and should still be able to consume it.
|
||||
clientTransport.enableDataNotification();
|
||||
|
||||
@@ -10,7 +10,8 @@ enum CaretChangedReason {
|
||||
"longpressonemptycontent",
|
||||
"taponcaret",
|
||||
"presscaret",
|
||||
"releasecaret"
|
||||
"releasecaret",
|
||||
"scroll"
|
||||
};
|
||||
|
||||
dictionary CaretStateChangedEventInit : EventInit {
|
||||
|
||||
@@ -43,7 +43,7 @@ interface PresentationConnection : EventTarget {
|
||||
*
|
||||
* This function only works when the state is "connected".
|
||||
*
|
||||
* TODO bug 1148307 Implement PresentationSessionTransport with DataChannel to
|
||||
* TODO bug 1228474 Implement PresentationSessionTransport with DataChannel to
|
||||
* support other binary types.
|
||||
*/
|
||||
[Throws]
|
||||
|
||||
@@ -43,4 +43,24 @@ interface PresentationRequest : EventTarget {
|
||||
* The event is fired for all connections that are created for the controller.
|
||||
*/
|
||||
attribute EventHandler onconnectionavailable;
|
||||
|
||||
/*
|
||||
* A chrome page, or page which has presentation-device-manage permissiongs,
|
||||
* uses startWithDevice() to start a new connection with specified device,
|
||||
* and it will be returned with the promise. UA may show a prompt box with a
|
||||
* list of available devices and ask the user to grant permission, choose a
|
||||
* device, or cancel the operation.
|
||||
*
|
||||
* The promise is resolved when the presenting page is successfully loaded and
|
||||
* the communication channel is established, i.e., the connection state is
|
||||
* "connected".
|
||||
*
|
||||
* The promise may be rejected duo to one of the following reasons:
|
||||
* - "OperationError": Unexpected error occurs.
|
||||
* - "NotFoundError": No available device.
|
||||
* - "NetworkError": Failed to establish the control channel or data channel.
|
||||
* - "TimeoutError": Presenting page takes too long to load.
|
||||
*/
|
||||
[CheckAnyPermissions="presentation-device-manage", Throws]
|
||||
Promise<PresentationConnection> startWithDevice(DOMString deviceId);
|
||||
};
|
||||
|
||||
@@ -45,6 +45,7 @@ nsComposeTxtSrvFilter::Skip(nsIDOMNode* aNode, bool *_retval)
|
||||
} else if (content->IsAnyOfHTMLElements(nsGkAtoms::script,
|
||||
nsGkAtoms::textarea,
|
||||
nsGkAtoms::select,
|
||||
nsGkAtoms::style,
|
||||
nsGkAtoms::map)) {
|
||||
*_retval = true;
|
||||
} else if (content->IsHTMLElement(nsGkAtoms::table)) {
|
||||
|
||||
@@ -6,3 +6,4 @@ skip-if = buildapp == 'b2g'
|
||||
[test_bug434998.xul]
|
||||
[test_bug678842.html]
|
||||
[test_bug717433.html]
|
||||
[test_bug1219928.html]
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!--
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1219928
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1219928</title>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1219928">Mozilla Bug 1219928</a>
|
||||
<p id="display"></p>
|
||||
|
||||
<div contenteditable id="en-US" lang="en-US">
|
||||
<p>And here a missspelled word</p>
|
||||
<style>
|
||||
<!-- and here another onnee in a style comment -->
|
||||
</style>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
/** Test for Bug 1219928 **/
|
||||
/* Very simple test to check that <style> blocks are skipped in the spell check */
|
||||
|
||||
var spellchecker;
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SimpleTest.waitForFocus(function() {
|
||||
Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm");
|
||||
|
||||
var elem = document.getElementById('en-US');
|
||||
elem.focus();
|
||||
|
||||
onSpellCheck(elem, function () {
|
||||
var Ci = Components.interfaces;
|
||||
var editingSession = window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIEditingSession);
|
||||
var editor = editingSession.getEditorForWindow(window);
|
||||
var selcon = editor.selectionController;
|
||||
var sel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
|
||||
|
||||
is(sel.toString(), "missspelled", "one misspelled word expected: missspelled");
|
||||
|
||||
spellchecker = Components.classes['@mozilla.org/editor/editorspellchecker;1'].createInstance(Components.interfaces.nsIEditorSpellCheck);
|
||||
var filterContractId = "@mozilla.org/editor/txtsrvfilter;1";
|
||||
spellchecker.setFilter(Components.classes[filterContractId].createInstance(Components.interfaces.nsITextServicesFilter));
|
||||
spellchecker.InitSpellChecker(editor, false, spellCheckStarted);
|
||||
});
|
||||
});
|
||||
|
||||
function spellCheckStarted() {
|
||||
var misspelledWord = spellchecker.GetNextMisspelledWord();
|
||||
is(misspelledWord, "missspelled", "first misspelled word expected: missspelled");
|
||||
|
||||
// Without the fix, the next misspelled word was 'onnee', so we check that we don't get it.
|
||||
misspelledWord = spellchecker.GetNextMisspelledWord();
|
||||
isnot(misspelledWord, "onnee", "second misspelled word should not be: onnee");
|
||||
|
||||
spellchecker = "";
|
||||
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
@@ -31,8 +31,8 @@ interface nsIWebBrowserChrome : nsISupports
|
||||
/**
|
||||
* The currently loaded WebBrowser. The browser chrome may be
|
||||
* told to set the WebBrowser object to a new object by setting this
|
||||
* attribute. In this case the implementer is responsible for taking the
|
||||
* new WebBrowser object and doing any necessary initialization or setup
|
||||
* attribute. In this case the implementer is responsible for taking the
|
||||
* new WebBrowser object and doing any necessary initialization or setup
|
||||
* as if it had created the WebBrowser itself. This includes positioning
|
||||
* setting up listeners etc.
|
||||
*/
|
||||
@@ -53,7 +53,7 @@ interface nsIWebBrowserChrome : nsISupports
|
||||
const unsigned long CHROME_SCROLLBARS = 0x00000200;
|
||||
const unsigned long CHROME_TITLEBAR = 0x00000400;
|
||||
const unsigned long CHROME_EXTRA = 0x00000800;
|
||||
|
||||
|
||||
// createBrowserWindow specific flags
|
||||
const unsigned long CHROME_WITH_SIZE = 0x00001000;
|
||||
const unsigned long CHROME_WITH_POSITION = 0x00002000;
|
||||
@@ -98,12 +98,12 @@ interface nsIWebBrowserChrome : nsISupports
|
||||
|
||||
// Note: The modal style bit just affects the way the window looks and does
|
||||
// mean it's actually modal.
|
||||
const unsigned long CHROME_MODAL = 0x20000000;
|
||||
const unsigned long CHROME_MODAL = 0x20000000;
|
||||
const unsigned long CHROME_OPENAS_DIALOG = 0x40000000;
|
||||
const unsigned long CHROME_OPENAS_CHROME = 0x80000000;
|
||||
|
||||
|
||||
const unsigned long CHROME_ALL = 0x00000ffe;
|
||||
|
||||
|
||||
/**
|
||||
* The chrome flags for this browser chrome. The implementation should
|
||||
* reflect the value of this attribute by hiding or showing its chrome
|
||||
@@ -118,20 +118,20 @@ interface nsIWebBrowserChrome : nsISupports
|
||||
void destroyBrowserWindow();
|
||||
|
||||
/**
|
||||
* Tells the chrome to size itself such that the browser will be the
|
||||
* Tells the chrome to size itself such that the browser will be the
|
||||
* specified size.
|
||||
* @param aCX new width of the browser
|
||||
* @param aCY new height of the browser
|
||||
*/
|
||||
void sizeBrowserTo(in long aCX, in long aCY);
|
||||
|
||||
|
||||
/**
|
||||
* Shows the window as a modal window.
|
||||
* @return (the function error code) the status value specified by
|
||||
* in exitModalEventLoop.
|
||||
*/
|
||||
void showAsModal();
|
||||
|
||||
|
||||
/**
|
||||
* Is the window modal (that is, currently executing a modal loop)?
|
||||
* @return true if it's a modal window
|
||||
@@ -145,4 +145,3 @@ interface nsIWebBrowserChrome : nsISupports
|
||||
*/
|
||||
void exitModalEventLoop(in nsresult aStatus);
|
||||
};
|
||||
|
||||
|
||||
@@ -45,7 +45,6 @@ static const uint32_t ACCEPT_FOR_N_DAYS = 3;
|
||||
static const bool kDefaultPolicy = true;
|
||||
static const char kCookiesLifetimePolicy[] = "network.cookie.lifetimePolicy";
|
||||
static const char kCookiesLifetimeDays[] = "network.cookie.lifetime.days";
|
||||
static const char kCookiesAlwaysAcceptSession[] = "network.cookie.alwaysAcceptSessionCookies";
|
||||
|
||||
static const char kCookiesPrefsMigrated[] = "network.cookie.prefsMigrated";
|
||||
// obsolete pref names for migration
|
||||
@@ -76,7 +75,6 @@ nsCookiePermission::Init()
|
||||
if (prefBranch) {
|
||||
prefBranch->AddObserver(kCookiesLifetimePolicy, this, false);
|
||||
prefBranch->AddObserver(kCookiesLifetimeDays, this, false);
|
||||
prefBranch->AddObserver(kCookiesAlwaysAcceptSession, this, false);
|
||||
PrefChanged(prefBranch, nullptr);
|
||||
|
||||
// migration code for original cookie prefs
|
||||
@@ -112,8 +110,7 @@ nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
|
||||
|
||||
if (PREF_CHANGED(kCookiesLifetimePolicy) &&
|
||||
NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimePolicy, &val))) {
|
||||
if (val != static_cast<int32_t>(ACCEPT_SESSION) &&
|
||||
val != static_cast<int32_t>(ACCEPT_FOR_N_DAYS)) {
|
||||
if (val != static_cast<int32_t>(ACCEPT_SESSION) && val != static_cast<int32_t>(ACCEPT_FOR_N_DAYS)) {
|
||||
val = ACCEPT_NORMALLY;
|
||||
}
|
||||
mCookiesLifetimePolicy = val;
|
||||
@@ -122,12 +119,7 @@ nsCookiePermission::PrefChanged(nsIPrefBranch *aPrefBranch,
|
||||
if (PREF_CHANGED(kCookiesLifetimeDays) &&
|
||||
NS_SUCCEEDED(aPrefBranch->GetIntPref(kCookiesLifetimeDays, &val)))
|
||||
// save cookie lifetime in seconds instead of days
|
||||
mCookiesLifetimeSec = val * 24 * 60 * 60;
|
||||
|
||||
bool bval;
|
||||
if (PREF_CHANGED(kCookiesAlwaysAcceptSession) &&
|
||||
NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookiesAlwaysAcceptSession, &bval)))
|
||||
mCookiesAlwaysAcceptSession = bval;
|
||||
mCookiesLifetimeSec = (int64_t)val * 24 * 60 * 60;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
@@ -235,23 +227,23 @@ nsCookiePermission::CanSetCookie(nsIURI *aURI,
|
||||
break;
|
||||
|
||||
default:
|
||||
// The permission manager has nothing to say about this cookie
|
||||
// so we apply the default prefs to it.
|
||||
// the permission manager has nothing to say about this cookie -
|
||||
// so, we apply the default prefs to it.
|
||||
NS_ASSERTION(perm == nsIPermissionManager::UNKNOWN_ACTION, "unknown permission");
|
||||
|
||||
// Now we need to figure out what type of accept policy we're dealing with.
|
||||
// If we accept cookies normally, just bail and return.
|
||||
// now we need to figure out what type of accept policy we're dealing with
|
||||
// if we accept cookies normally, just bail and return
|
||||
if (mCookiesLifetimePolicy == ACCEPT_NORMALLY) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Declare this here since it'll be used in all of the remaining cases.
|
||||
// declare this here since it'll be used in all of the remaining cases
|
||||
int64_t currentTime = PR_Now() / PR_USEC_PER_SEC;
|
||||
int64_t delta = *aExpiry - currentTime;
|
||||
|
||||
// We are accepting the cookie, but if it's not a session cookie,
|
||||
// we may have to limit its lifetime.
|
||||
// We are accepting the cookie, but,
|
||||
// if it's not a session cookie, we may have to limit its lifetime.
|
||||
if (!*aIsSession && delta > 0) {
|
||||
if (mCookiesLifetimePolicy == ACCEPT_SESSION) {
|
||||
// limit lifetime to session
|
||||
|
||||
@@ -24,7 +24,6 @@ public:
|
||||
nsCookiePermission()
|
||||
: mCookiesLifetimeSec(INT64_MAX)
|
||||
, mCookiesLifetimePolicy(0) // ACCEPT_NORMALLY
|
||||
, mCookiesAlwaysAcceptSession(false)
|
||||
{}
|
||||
|
||||
bool Init();
|
||||
@@ -40,7 +39,6 @@ private:
|
||||
|
||||
int64_t mCookiesLifetimeSec; // lifetime limit specified in seconds
|
||||
uint8_t mCookiesLifetimePolicy; // pref for how long cookies are stored
|
||||
bool mCookiesAlwaysAcceptSession; // don't prompt for session cookies
|
||||
};
|
||||
|
||||
// {EF565D0A-AB9A-4A13-9160-0644CDFD859A}
|
||||
|
||||
@@ -77,6 +77,14 @@ JobScheduler::SubmitJob(Job* aJob)
|
||||
GetQueueForJob(aJob)->SubmitJob(aJob);
|
||||
}
|
||||
|
||||
void
|
||||
JobScheduler::Join(SyncObject* aCompletion)
|
||||
{
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
JobScheduler::SubmitJob(new SetEventJob(waitForCompletion, aCompletion));
|
||||
waitForCompletion->Wait();
|
||||
}
|
||||
|
||||
MultiThreadedJobQueue*
|
||||
JobScheduler::GetQueueForJob(Job* aJob)
|
||||
{
|
||||
|
||||
@@ -71,6 +71,12 @@ public:
|
||||
/// The caller looses ownership of the task buffer.
|
||||
static void SubmitJob(Job* aJobs);
|
||||
|
||||
/// Convenience function to block the current thread until a given SyncObject
|
||||
/// is in the signaled state.
|
||||
///
|
||||
/// The current thread will first try to steal jobs before blocking.
|
||||
static void Join(SyncObject* aCompletionSync);
|
||||
|
||||
/// Process commands until the command buffer needs to block on a sync object,
|
||||
/// completes, yields, or encounters an error.
|
||||
///
|
||||
|
||||
@@ -132,9 +132,14 @@ MultiThreadedJobQueue::RegisterThread()
|
||||
void
|
||||
MultiThreadedJobQueue::UnregisterThread()
|
||||
{
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
mSection.Enter();
|
||||
mThreadsCount -= 1;
|
||||
if (mThreadsCount == 0) {
|
||||
bool finishShutdown = mThreadsCount == 0;
|
||||
mSection.Leave();
|
||||
|
||||
if (finishShutdown) {
|
||||
// Can't touch mSection or any other member from now on because this object
|
||||
// may get deleted on the main thread after mShutdownEvent is set.
|
||||
::SetEvent(mShutdownEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,6 +548,24 @@ public:
|
||||
_33 = 1.0f;
|
||||
_43 = 0.0f;
|
||||
_34 = 0.0f;
|
||||
// Some matrices, such as those derived from perspective transforms,
|
||||
// can modify _44 from 1, while leaving the rest of the fourth column
|
||||
// (_14, _24) at 0. In this case, after resetting the third row and
|
||||
// third column above, the value of _44 functions only to scale the
|
||||
// coordinate transform divide by W. The matrix can be converted to
|
||||
// a true 2D matrix by normalizing out the scaling effect of _44 on
|
||||
// the remaining components ahead of time.
|
||||
if (_14 == 0.0f && _24 == 0.0f &&
|
||||
_44 != 1.0f && _44 != 0.0f) {
|
||||
Float scale = 1.0f / _44;
|
||||
_11 *= scale;
|
||||
_12 *= scale;
|
||||
_21 *= scale;
|
||||
_22 *= scale;
|
||||
_41 *= scale;
|
||||
_42 *= scale;
|
||||
_44 = 1.0f;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ struct SanityChecker {
|
||||
{
|
||||
MaybeYieldThread();
|
||||
CriticalSectionAutoEnter lock(&mSection);
|
||||
ASSERT_EQ(mAdvancements[aJobId], aCmdId-1);
|
||||
MOZ_RELEASE_ASSERT(mAdvancements[aJobId] == aCmdId-1);
|
||||
mAdvancements[aJobId] = aCmdId;
|
||||
}
|
||||
};
|
||||
@@ -66,12 +66,12 @@ struct JoinTestSanityCheck : public SanityChecker {
|
||||
{
|
||||
// Job 0 is the special task executed when everything is joined after task 1
|
||||
if (aCmdId == 0) {
|
||||
ASSERT_FALSE(mSpecialJobHasRun);
|
||||
MOZ_RELEASE_ASSERT(!mSpecialJobHasRun);
|
||||
mSpecialJobHasRun = true;
|
||||
for (auto advancement : mAdvancements) {
|
||||
// Because of the synchronization point (beforeFilter), all
|
||||
// task buffers should have run task 1 when task 0 is run.
|
||||
ASSERT_EQ(advancement, (uint32_t)1);
|
||||
MOZ_RELEASE_ASSERT(advancement == 1);
|
||||
}
|
||||
} else {
|
||||
// This check does not apply to task 0.
|
||||
@@ -79,7 +79,7 @@ struct JoinTestSanityCheck : public SanityChecker {
|
||||
}
|
||||
|
||||
if (aCmdId == 2) {
|
||||
ASSERT_TRUE(mSpecialJobHasRun);
|
||||
MOZ_RELEASE_ASSERT(mSpecialJobHasRun);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -145,18 +145,12 @@ void TestSchedulerJoin(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
|
||||
}
|
||||
completion->FreezePrerequisites();
|
||||
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
auto evtJob = new SetEventJob(waitForCompletion, completion);
|
||||
JobScheduler::SubmitJob(evtJob);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
waitForCompletion->Wait();
|
||||
JobScheduler::Join(completion);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
for (auto advancement : check.mAdvancements) {
|
||||
ASSERT_TRUE(advancement == 2);
|
||||
EXPECT_TRUE(advancement == 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,21 +199,25 @@ void TestSchedulerChain(uint32_t aNumThreads, uint32_t aNumCmdBuffers)
|
||||
}
|
||||
completion->FreezePrerequisites();
|
||||
|
||||
RefPtr<EventObject> waitForCompletion = new EventObject();
|
||||
auto evtJob = new SetEventJob(waitForCompletion, completion);
|
||||
JobScheduler::SubmitJob(evtJob);
|
||||
|
||||
MaybeYieldThread();
|
||||
|
||||
waitForCompletion->Wait();
|
||||
JobScheduler::Join(completion);
|
||||
|
||||
for (auto advancement : check.mAdvancements) {
|
||||
ASSERT_TRUE(advancement == numJobs);
|
||||
EXPECT_TRUE(advancement == numJobs);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace test_scheduler
|
||||
|
||||
TEST(Moz2D, JobScheduler_Shutdown) {
|
||||
srand(time(nullptr));
|
||||
for (uint32_t threads = 1; threads < 16; ++threads) {
|
||||
for (uint32_t i = 1; i < 1000; ++i) {
|
||||
mozilla::gfx::JobScheduler::Init(threads, threads);
|
||||
mozilla::gfx::JobScheduler::ShutDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Moz2D, JobScheduler_Join) {
|
||||
srand(time(nullptr));
|
||||
for (uint32_t threads = 1; threads < 8; ++threads) {
|
||||
|
||||
@@ -361,23 +361,6 @@ gfxAndroidPlatform::RequiresLinearZoom()
|
||||
return gfxPlatform::RequiresLinearZoom();
|
||||
}
|
||||
|
||||
bool
|
||||
gfxAndroidPlatform::UseAcceleratedSkiaCanvas()
|
||||
{
|
||||
return HaveChoiceOfHWAndSWCanvas() && gfxPlatform::UseAcceleratedSkiaCanvas();
|
||||
}
|
||||
|
||||
bool gfxAndroidPlatform::HaveChoiceOfHWAndSWCanvas()
|
||||
{
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
if (!AndroidBridge::Bridge() || AndroidBridge::Bridge()->GetAPIVersion() < 11) {
|
||||
// It's slower than software due to not having a compositing fast path
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return gfxPlatform::HaveChoiceOfHWAndSWCanvas();
|
||||
}
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
class GonkVsyncSource final : public VsyncSource
|
||||
{
|
||||
|
||||
@@ -66,8 +66,6 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual bool HaveChoiceOfHWAndSWCanvas() override;
|
||||
virtual bool UseAcceleratedSkiaCanvas() override;
|
||||
virtual already_AddRefed<mozilla::gfx::VsyncSource> CreateHardwareVsyncSource() override;
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
#include "nsFrameSelection.h"
|
||||
#include "nsGenericHTMLElement.h"
|
||||
#include "nsIHapticFeedback.h"
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
#include "nsWindow.h"
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -69,13 +72,17 @@ AccessibleCaretManager::sSelectionBarEnabled = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sCaretsExtendedVisibility = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sCaretsAlwaysTilt = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = true;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sCaretsScriptUpdates = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sHapticFeedback = false;
|
||||
/*static*/ bool
|
||||
AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
|
||||
|
||||
AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
|
||||
: mPresShell(aPresShell)
|
||||
@@ -95,14 +102,18 @@ AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
|
||||
"layout.accessiblecaret.bar.enabled");
|
||||
Preferences::AddBoolVarCache(&sCaretShownWhenLongTappingOnEmptyContent,
|
||||
"layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content");
|
||||
Preferences::AddBoolVarCache(&sCaretsExtendedVisibility,
|
||||
"layout.accessiblecaret.extendedvisibility");
|
||||
Preferences::AddBoolVarCache(&sCaretsAlwaysTilt,
|
||||
"layout.accessiblecaret.always_tilt");
|
||||
Preferences::AddBoolVarCache(&sCaretsAlwaysShowWhenScrolling,
|
||||
"layout.accessiblecaret.always_show_when_scrolling", true);
|
||||
Preferences::AddBoolVarCache(&sCaretsScriptUpdates,
|
||||
"layout.accessiblecaret.allow_script_change_updates");
|
||||
Preferences::AddBoolVarCache(&sCaretsAllowDraggingAcrossOtherCaret,
|
||||
"layout.accessiblecaret.allow_dragging_across_other_caret", true);
|
||||
Preferences::AddBoolVarCache(&sHapticFeedback,
|
||||
"layout.accessiblecaret.hapticfeedback");
|
||||
Preferences::AddBoolVarCache(&sExtendSelectionForPhoneNumber,
|
||||
"layout.accessiblecaret.extend_selection_for_phone_number");
|
||||
addedPrefs = true;
|
||||
}
|
||||
}
|
||||
@@ -197,18 +208,6 @@ AccessibleCaretManager::HideCarets()
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::DoNotShowCarets()
|
||||
{
|
||||
if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
|
||||
AC_LOG("%s", __FUNCTION__);
|
||||
mFirstCaret->SetAppearance(Appearance::NormalNotShown);
|
||||
mSecondCaret->SetAppearance(Appearance::NormalNotShown);
|
||||
DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
|
||||
CancelCaretTimeoutTimer();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::UpdateCarets(UpdateCaretsHint aHint)
|
||||
{
|
||||
@@ -345,10 +344,12 @@ AccessibleCaretManager::UpdateCaretsForSelectionMode(UpdateCaretsHint aHint)
|
||||
AC_LOG("%s: selection: %p", __FUNCTION__, GetSelection());
|
||||
|
||||
int32_t startOffset = 0;
|
||||
nsIFrame* startFrame = FindFirstNodeWithFrame(false, &startOffset);
|
||||
nsIFrame* startFrame =
|
||||
GetFrameForFirstRangeStartOrLastRangeEnd(eDirNext, &startOffset);
|
||||
|
||||
int32_t endOffset = 0;
|
||||
nsIFrame* endFrame = FindFirstNodeWithFrame(true, &endOffset);
|
||||
nsIFrame* endFrame =
|
||||
GetFrameForFirstRangeStartOrLastRangeEnd(eDirPrevious, &endOffset);
|
||||
|
||||
if (!CompareTreePosition(startFrame, endFrame)) {
|
||||
// XXX: Do we really have to hide carets if this condition isn't satisfied?
|
||||
@@ -620,14 +621,19 @@ AccessibleCaretManager::OnScrollStart()
|
||||
{
|
||||
AC_LOG("%s", __FUNCTION__);
|
||||
|
||||
mFirstCaretAppearanceOnScrollStart = mFirstCaret->GetAppearance();
|
||||
mSecondCaretAppearanceOnScrollStart = mSecondCaret->GetAppearance();
|
||||
|
||||
// Hide the carets. (Extended visibility makes them "NormalNotShown").
|
||||
if (sCaretsExtendedVisibility) {
|
||||
DoNotShowCarets();
|
||||
} else {
|
||||
if (!sCaretsAlwaysShowWhenScrolling) {
|
||||
// Backup the appearance so that we can restore them after the scrolling
|
||||
// ends.
|
||||
mFirstCaretAppearanceOnScrollStart = mFirstCaret->GetAppearance();
|
||||
mSecondCaretAppearanceOnScrollStart = mSecondCaret->GetAppearance();
|
||||
HideCarets();
|
||||
return;
|
||||
}
|
||||
|
||||
if (mFirstCaret->IsLogicallyVisible() || mSecondCaret->IsLogicallyVisible()) {
|
||||
// Dispatch the event only if one of the carets is logically visible like in
|
||||
// HideCarets().
|
||||
DispatchCaretStateChangedEvent(CaretChangedReason::Scroll);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -638,8 +644,11 @@ AccessibleCaretManager::OnScrollEnd()
|
||||
return;
|
||||
}
|
||||
|
||||
mFirstCaret->SetAppearance(mFirstCaretAppearanceOnScrollStart);
|
||||
mSecondCaret->SetAppearance(mSecondCaretAppearanceOnScrollStart);
|
||||
if (!sCaretsAlwaysShowWhenScrolling) {
|
||||
// Restore the appearance which is saved before the scrolling is started.
|
||||
mFirstCaret->SetAppearance(mFirstCaretAppearanceOnScrollStart);
|
||||
mSecondCaret->SetAppearance(mSecondCaretAppearanceOnScrollStart);
|
||||
}
|
||||
|
||||
if (GetCaretMode() == CaretMode::Cursor) {
|
||||
if (!mFirstCaret->IsLogicallyVisible()) {
|
||||
@@ -818,6 +827,11 @@ AccessibleCaretManager::SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) cons
|
||||
SetSelectionDragState(false);
|
||||
ClearMaintainedSelection();
|
||||
|
||||
// Smart-select phone numbers if possible.
|
||||
if (sExtendSelectionForPhoneNumber) {
|
||||
SelectMoreIfPhoneNumber();
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
@@ -828,6 +842,62 @@ AccessibleCaretManager::SetSelectionDragState(bool aState) const
|
||||
if (fs) {
|
||||
fs->SetDragState(aState);
|
||||
}
|
||||
|
||||
// Pin Fennecs DynamicToolbarAnimator in place before/after dragging,
|
||||
// to avoid co-incident screen scrolling.
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
nsIDocument* doc = mPresShell->GetDocument();
|
||||
MOZ_ASSERT(doc);
|
||||
nsIWidget* widget = nsContentUtils::WidgetForDocument(doc);
|
||||
static_cast<nsWindow*>(widget)->SetSelectionDragState(aState);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::SelectMoreIfPhoneNumber() const
|
||||
{
|
||||
SetSelectionDirection(eDirNext);
|
||||
ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
|
||||
|
||||
SetSelectionDirection(eDirPrevious);
|
||||
ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
|
||||
}
|
||||
|
||||
void
|
||||
AccessibleCaretManager::ExtendPhoneNumberSelection(const nsAString& aDirection) const
|
||||
{
|
||||
nsIDocument* doc = mPresShell->GetDocument();
|
||||
|
||||
// Extend the phone number selection until we find a boundary.
|
||||
Selection* selection = GetSelection();
|
||||
|
||||
while (selection) {
|
||||
// Save current Focus position, and extend the selection one char.
|
||||
nsINode* focusNode = selection->GetFocusNode();
|
||||
uint32_t focusOffset = selection->FocusOffset();
|
||||
selection->Modify(NS_LITERAL_STRING("extend"),
|
||||
aDirection,
|
||||
NS_LITERAL_STRING("character"));
|
||||
|
||||
// If the selection didn't change, (can't extend further), we're done.
|
||||
if (selection->GetFocusNode() == focusNode &&
|
||||
selection->FocusOffset() == focusOffset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the changed selection isn't a valid phone number, we're done.
|
||||
nsAutoString selectedText;
|
||||
selection->Stringify(selectedText);
|
||||
nsAutoString phoneRegex(NS_LITERAL_STRING("(^\\+)?[0-9\\s,\\-.()*#pw]{1,30}$"));
|
||||
|
||||
if (!nsContentUtils::IsPatternMatching(selectedText, phoneRegex, doc)) {
|
||||
// Backout the undesired selection extend, (collapse to original
|
||||
// Anchor, extend to original Focus), before exit.
|
||||
selection->Collapse(selection->GetAnchorNode(), selection->AnchorOffset());
|
||||
selection->Extend(focusNode, focusOffset);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -859,41 +929,51 @@ AccessibleCaretManager::FlushLayout() const
|
||||
}
|
||||
|
||||
nsIFrame*
|
||||
AccessibleCaretManager::FindFirstNodeWithFrame(bool aBackward,
|
||||
int32_t* aOutOffset) const
|
||||
AccessibleCaretManager::GetFrameForFirstRangeStartOrLastRangeEnd(
|
||||
nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode,
|
||||
int32_t* aOutNodeOffset) const
|
||||
{
|
||||
if (!mPresShell) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
|
||||
|
||||
nsRange* range = nullptr;
|
||||
RefPtr<nsINode> startNode;
|
||||
RefPtr<nsINode> endNode;
|
||||
int32_t nodeOffset = 0;
|
||||
CaretAssociationHint hint;
|
||||
|
||||
RefPtr<Selection> selection = GetSelection();
|
||||
if (!selection) {
|
||||
return nullptr;
|
||||
bool findInFirstRangeStart = aDirection == eDirNext;
|
||||
|
||||
if (findInFirstRangeStart) {
|
||||
range = selection->GetRangeAt(0);
|
||||
startNode = range->GetStartParent();
|
||||
endNode = range->GetEndParent();
|
||||
nodeOffset = range->StartOffset();
|
||||
hint = CARET_ASSOCIATE_AFTER;
|
||||
} else {
|
||||
range = selection->GetRangeAt(selection->RangeCount() - 1);
|
||||
startNode = range->GetEndParent();
|
||||
endNode = range->GetStartParent();
|
||||
nodeOffset = range->EndOffset();
|
||||
hint = CARET_ASSOCIATE_BEFORE;
|
||||
}
|
||||
|
||||
RefPtr<nsFrameSelection> fs = GetFrameSelection();
|
||||
if (!fs) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
uint32_t rangeCount = selection->RangeCount();
|
||||
if (rangeCount <= 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRange* range = selection->GetRangeAt(aBackward ? rangeCount - 1 : 0);
|
||||
RefPtr<nsINode> startNode =
|
||||
aBackward ? range->GetEndParent() : range->GetStartParent();
|
||||
RefPtr<nsINode> endNode =
|
||||
aBackward ? range->GetStartParent() : range->GetEndParent();
|
||||
int32_t offset = aBackward ? range->EndOffset() : range->StartOffset();
|
||||
nsCOMPtr<nsIContent> startContent = do_QueryInterface(startNode);
|
||||
CaretAssociationHint hintStart =
|
||||
aBackward ? CARET_ASSOCIATE_BEFORE : CARET_ASSOCIATE_AFTER;
|
||||
RefPtr<nsFrameSelection> fs = GetFrameSelection();
|
||||
nsIFrame* startFrame =
|
||||
fs->GetFrameForNodeOffset(startContent, offset, hintStart, aOutOffset);
|
||||
fs->GetFrameForNodeOffset(startContent, nodeOffset, hint, aOutOffset);
|
||||
|
||||
if (startFrame) {
|
||||
if (aOutNode) {
|
||||
*aOutNode = startNode.get();
|
||||
}
|
||||
if (aOutNodeOffset) {
|
||||
*aOutNodeOffset = nodeOffset;
|
||||
}
|
||||
return startFrame;
|
||||
}
|
||||
|
||||
@@ -907,7 +987,8 @@ AccessibleCaretManager::FindFirstNodeWithFrame(bool aBackward,
|
||||
|
||||
startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
|
||||
while (!startFrame && startNode != endNode) {
|
||||
startNode = aBackward ? walker->PreviousNode(err) : walker->NextNode(err);
|
||||
startNode = findInFirstRangeStart ? walker->NextNode(err)
|
||||
: walker->PreviousNode(err);
|
||||
|
||||
if (!startNode) {
|
||||
break;
|
||||
@@ -920,78 +1001,86 @@ AccessibleCaretManager::FindFirstNodeWithFrame(bool aBackward,
|
||||
}
|
||||
|
||||
bool
|
||||
AccessibleCaretManager::CompareRangeWithContentOffset(nsIFrame::ContentOffsets& aOffsets)
|
||||
AccessibleCaretManager::RestrictCaretDraggingOffsets(
|
||||
nsIFrame::ContentOffsets& aOffsets)
|
||||
{
|
||||
Selection* selection = GetSelection();
|
||||
if (!selection) {
|
||||
if (!mPresShell) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t rangeCount = selection->RangeCount();
|
||||
MOZ_ASSERT(rangeCount > 0);
|
||||
|
||||
int32_t rangeIndex = (mActiveCaret == mFirstCaret.get() ? rangeCount - 1 : 0);
|
||||
RefPtr<nsRange> range = selection->GetRangeAt(rangeIndex);
|
||||
MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
|
||||
|
||||
nsDirection dir = mActiveCaret == mFirstCaret.get() ? eDirPrevious : eDirNext;
|
||||
int32_t offset = 0;
|
||||
nsINode* node = nullptr;
|
||||
int32_t nodeOffset = 0;
|
||||
CaretAssociationHint hint;
|
||||
nsDirection dir;
|
||||
int32_t contentOffset = 0;
|
||||
nsIFrame* frame =
|
||||
GetFrameForFirstRangeStartOrLastRangeEnd(dir, &offset, &node, &contentOffset);
|
||||
|
||||
if (mActiveCaret == mFirstCaret.get()) {
|
||||
// Check previous character of end node offset
|
||||
node = range->GetEndParent();
|
||||
nodeOffset = range->EndOffset();
|
||||
hint = CARET_ASSOCIATE_BEFORE;
|
||||
dir = eDirPrevious;
|
||||
} else {
|
||||
// Check next character of start node offset
|
||||
node = range->GetStartParent();
|
||||
nodeOffset = range->StartOffset();
|
||||
hint = CARET_ASSOCIATE_AFTER;
|
||||
dir = eDirNext;
|
||||
if (!frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIContent> content = do_QueryInterface(node);
|
||||
|
||||
RefPtr<nsFrameSelection> fs = GetFrameSelection();
|
||||
if (!fs) {
|
||||
return false;
|
||||
}
|
||||
// Compare the active caret's new position (aOffsets) to the inactive caret's
|
||||
// position.
|
||||
int32_t cmpToInactiveCaretPos =
|
||||
nsContentUtils::ComparePoints(aOffsets.content, aOffsets.StartOffset(),
|
||||
content, contentOffset);
|
||||
|
||||
int32_t offset = 0;
|
||||
nsIFrame* theFrame =
|
||||
fs->GetFrameForNodeOffset(content, nodeOffset, hint, &offset);
|
||||
|
||||
if (!theFrame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move one character forward/backward from point and get offset
|
||||
nsPeekOffsetStruct pos(eSelectCluster,
|
||||
dir,
|
||||
offset,
|
||||
nsPoint(0, 0),
|
||||
true,
|
||||
true, //limit on scrolled views
|
||||
false,
|
||||
false,
|
||||
false);
|
||||
nsresult rv = theFrame->PeekOffset(&pos);
|
||||
// Move one character (in the direction of dir) from the inactive caret's
|
||||
// position. This is the limit for the active caret's new position.
|
||||
nsPeekOffsetStruct limit(eSelectCluster, dir, offset, nsPoint(0, 0), true, true,
|
||||
false, false, false);
|
||||
nsresult rv = frame->PeekOffset(&limit);
|
||||
if (NS_FAILED(rv)) {
|
||||
pos.mResultContent = content;
|
||||
pos.mContentOffset = nodeOffset;
|
||||
limit.mResultContent = content;
|
||||
limit.mContentOffset = contentOffset;
|
||||
}
|
||||
|
||||
// Compare with current point
|
||||
int32_t result = nsContentUtils::ComparePoints(aOffsets.content,
|
||||
aOffsets.StartOffset(),
|
||||
pos.mResultContent,
|
||||
pos.mContentOffset);
|
||||
if ((mActiveCaret == mFirstCaret.get() && result == 1) ||
|
||||
(mActiveCaret == mSecondCaret.get() && result == -1)) {
|
||||
aOffsets.content = pos.mResultContent;
|
||||
aOffsets.offset = pos.mContentOffset;
|
||||
aOffsets.secondaryOffset = pos.mContentOffset;
|
||||
// Compare the active caret's new position (aOffsets) to the limit.
|
||||
int32_t cmpToLimit =
|
||||
nsContentUtils::ComparePoints(aOffsets.content, aOffsets.StartOffset(),
|
||||
limit.mResultContent, limit.mContentOffset);
|
||||
|
||||
auto SetOffsetsToLimit = [&aOffsets, &limit] () {
|
||||
aOffsets.content = limit.mResultContent;
|
||||
aOffsets.offset = limit.mContentOffset;
|
||||
aOffsets.secondaryOffset = limit.mContentOffset;
|
||||
};
|
||||
|
||||
if (!sCaretsAllowDraggingAcrossOtherCaret) {
|
||||
if ((mActiveCaret == mFirstCaret.get() && cmpToLimit == 1) ||
|
||||
(mActiveCaret == mSecondCaret.get() && cmpToLimit == -1)) {
|
||||
// The active caret's position is past the limit, which we don't allow
|
||||
// here. So set it to the limit, resulting in one character being
|
||||
// selected.
|
||||
SetOffsetsToLimit();
|
||||
}
|
||||
} else {
|
||||
switch (cmpToInactiveCaretPos) {
|
||||
case 0:
|
||||
// The active caret's position is the same as the position of the
|
||||
// inactive caret. So set it to the limit to prevent the selection from
|
||||
// being collapsed, resulting in one character being selected.
|
||||
SetOffsetsToLimit();
|
||||
break;
|
||||
case 1:
|
||||
if (mActiveCaret == mFirstCaret.get()) {
|
||||
// First caret was moved across the second caret. After making change
|
||||
// to the selection, the user will drag the second caret.
|
||||
mActiveCaret = mSecondCaret.get();
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
if (mActiveCaret == mSecondCaret.get()) {
|
||||
// Second caret was moved across the first caret. After making change
|
||||
// to the selection, the user will drag the first caret.
|
||||
mActiveCaret = mFirstCaret.get();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -1051,7 +1140,7 @@ AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint)
|
||||
|
||||
nsIFrame::ContentOffsets offsets =
|
||||
newFrame->GetContentOffsetsFromPoint(newPoint);
|
||||
if (!offsets.content) {
|
||||
if (offsets.IsNull()) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@@ -1061,7 +1150,7 @@ AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint)
|
||||
}
|
||||
|
||||
if (GetCaretMode() == CaretMode::Selection &&
|
||||
!CompareRangeWithContentOffset(offsets)) {
|
||||
!RestrictCaretDraggingOffsets(offsets)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@@ -1154,7 +1243,8 @@ AccessibleCaretManager::AdjustDragBoundary(const nsPoint& aPoint) const
|
||||
}
|
||||
}
|
||||
|
||||
if (GetCaretMode() == CaretMode::Selection) {
|
||||
if (GetCaretMode() == CaretMode::Selection &&
|
||||
!sCaretsAllowDraggingAcrossOtherCaret) {
|
||||
// Bug 1068474: Adjust the Y-coordinate so that the carets won't be in tilt
|
||||
// mode when a caret is being dragged surpass the other caret.
|
||||
//
|
||||
|
||||
@@ -135,10 +135,6 @@ protected:
|
||||
// Force hiding all carets regardless of the current selection status.
|
||||
void HideCarets();
|
||||
|
||||
// Force carets to be "present" logically, but not visible. Allows ActionBar
|
||||
// to stay open when carets visibility is supressed during scroll.
|
||||
void DoNotShowCarets();
|
||||
|
||||
void UpdateCaretsForCursorMode(UpdateCaretsHint aHint);
|
||||
void UpdateCaretsForSelectionMode(UpdateCaretsHint aHint);
|
||||
|
||||
@@ -155,12 +151,21 @@ protected:
|
||||
|
||||
nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
|
||||
void SetSelectionDragState(bool aState) const;
|
||||
|
||||
// Called to extend a selection if possible that it's a phone number.
|
||||
void SelectMoreIfPhoneNumber() const;
|
||||
// Extend the current phone number selection in the requested direction.
|
||||
void ExtendPhoneNumberSelection(const nsAString& aDirection) const;
|
||||
|
||||
void SetSelectionDirection(nsDirection aDir) const;
|
||||
|
||||
// If aBackward is false, find the first node from the first range in current
|
||||
// selection, and return the frame and the offset into that frame. If aBackward
|
||||
// is true, find the last node from the last range instead.
|
||||
nsIFrame* FindFirstNodeWithFrame(bool aBackward, int32_t* aOutOffset) const;
|
||||
// If aDirection is eDirNext, get the frame for the range start in the first
|
||||
// range from the current selection, and return the offset into that frame as
|
||||
// well as the range start node and the node offset. Otherwise, get the frame
|
||||
// and offset for the range end in the last range instead.
|
||||
nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd(
|
||||
nsDirection aDirection, int32_t* aOutOffset, nsINode** aOutNode = nullptr,
|
||||
int32_t* aOutNodeOffset = nullptr) const;
|
||||
|
||||
nsresult DragCaretInternal(const nsPoint& aPoint);
|
||||
nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
|
||||
@@ -179,11 +184,18 @@ protected:
|
||||
// be dragged. Returns the rect relative to aFrame.
|
||||
nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const;
|
||||
|
||||
// If we're dragging the first caret, we do not want to drag it over the
|
||||
// previous character of the second caret. Same as the second caret. So we
|
||||
// check if content offset exceeds the previous/next character of second/first
|
||||
// caret base the active caret.
|
||||
bool CompareRangeWithContentOffset(nsIFrame::ContentOffsets& aOffsets);
|
||||
// Restrict the active caret's dragging position based on
|
||||
// sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first
|
||||
// caret, the `limit` will be the previous character of the second caret.
|
||||
// Otherwise, the `limit` will be the next character of the first caret.
|
||||
//
|
||||
// @param aOffsets is the new position of the active caret, and it will be set
|
||||
// to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and
|
||||
// it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret
|
||||
// is true and the active caret's position is the same as the inactive's
|
||||
// position.
|
||||
// @return true if the aOffsets is suitable for changing the selection.
|
||||
bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets);
|
||||
|
||||
// Timeout in milliseconds to hide the AccessibleCaret under cursor mode while
|
||||
// no one touches it.
|
||||
@@ -272,25 +284,35 @@ protected:
|
||||
// selection bar is always disabled in cursor mode.
|
||||
static bool sSelectionBarEnabled;
|
||||
|
||||
// Preference to allow smarter selection of phone numbers,
|
||||
// when user long presses text to start.
|
||||
static bool sExtendSelectionForPhoneNumber;
|
||||
|
||||
// Preference to show caret in cursor mode when long tapping on an empty
|
||||
// content. This also changes the default update behavior in cursor mode,
|
||||
// which is based on the emptiness of the content, into something more
|
||||
// heuristic. See UpdateCaretsForCursorMode() for the details.
|
||||
static bool sCaretShownWhenLongTappingOnEmptyContent;
|
||||
|
||||
// Android specific visibility extensions correct compatibility issues
|
||||
// with ActionBar visibility during page scroll.
|
||||
static bool sCaretsExtendedVisibility;
|
||||
|
||||
// Preference to make carets always tilt in selection mode. By default, the
|
||||
// carets become tilt only when they are overlapping.
|
||||
static bool sCaretsAlwaysTilt;
|
||||
|
||||
// Preference to allow carets always show when scrolling (either panning or
|
||||
// zooming) the page. When set to false, carets will hide during scrolling,
|
||||
// and show again after the user lifts the finger off the screen.
|
||||
static bool sCaretsAlwaysShowWhenScrolling;
|
||||
|
||||
// By default, javascript content selection changes closes AccessibleCarets and
|
||||
// UI interactions. Optionally, we can try to maintain the active UI, keeping
|
||||
// carets and ActionBar available.
|
||||
static bool sCaretsScriptUpdates;
|
||||
|
||||
// Preference to allow one caret to be dragged across the other caret without
|
||||
// any limitation. When set to false, one caret cannot be dragged across the
|
||||
// other one.
|
||||
static bool sCaretsAllowDraggingAcrossOtherCaret;
|
||||
|
||||
// AccessibleCaret pref for haptic feedback behaviour on longPress.
|
||||
static bool sHapticFeedback;
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "nsStyleTransformMatrix.h"
|
||||
#include "nsTransitionManager.h"
|
||||
#include "nsDisplayList.h"
|
||||
#include "nsDOMCSSDeclaration.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -311,12 +312,21 @@ ActiveLayerTracker::NotifyOffsetRestyle(nsIFrame* aFrame)
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty)
|
||||
ActiveLayerTracker::NotifyAnimated(nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty,
|
||||
const nsAString& aNewValue,
|
||||
nsDOMCSSDeclaration* aDOMCSSDecl)
|
||||
{
|
||||
LayerActivity* layerActivity = GetLayerActivityForUpdate(aFrame);
|
||||
uint8_t& mutationCount = layerActivity->RestyleCountForProperty(aProperty);
|
||||
// We know this is animated, so just hack the mutation count.
|
||||
mutationCount = 0xFF;
|
||||
if (mutationCount != 0xFF) {
|
||||
nsAutoString oldValue;
|
||||
aDOMCSSDecl->GetPropertyValue(aProperty, oldValue);
|
||||
if (aNewValue != oldValue) {
|
||||
// We know this is animated, so just hack the mutation count.
|
||||
mutationCount = 0xFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
@@ -354,10 +364,12 @@ IsPresContextInScriptAnimationCallback(nsPresContext* aPresContext)
|
||||
|
||||
/* static */ void
|
||||
ActiveLayerTracker::NotifyInlineStyleRuleModified(nsIFrame* aFrame,
|
||||
nsCSSProperty aProperty)
|
||||
nsCSSProperty aProperty,
|
||||
const nsAString& aNewValue,
|
||||
nsDOMCSSDeclaration* aDOMCSSDecl)
|
||||
{
|
||||
if (IsPresContextInScriptAnimationCallback(aFrame->PresContext())) {
|
||||
NotifyAnimated(aFrame, aProperty);
|
||||
NotifyAnimated(aFrame, aProperty, aNewValue, aDOMCSSDecl);
|
||||
}
|
||||
if (gLayerActivityTracker &&
|
||||
gLayerActivityTracker->mCurrentScrollHandlerFrame.IsAlive()) {
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
class nsIFrame;
|
||||
class nsIContent;
|
||||
class nsDisplayListBuilder;
|
||||
class nsDOMCSSDeclaration;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
@@ -47,8 +48,12 @@ public:
|
||||
/**
|
||||
* Mark aFrame as being known to have an animation of aProperty.
|
||||
* Any such marking will time out after a short period.
|
||||
* aNewValue and aDOMCSSDecl are used to determine whether the property's
|
||||
* value has changed.
|
||||
*/
|
||||
static void NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty);
|
||||
static void NotifyAnimated(nsIFrame* aFrame, nsCSSProperty aProperty,
|
||||
const nsAString& aNewValue,
|
||||
nsDOMCSSDeclaration* aDOMCSSDecl);
|
||||
/**
|
||||
* Notify aFrame as being known to have an animation of aProperty through an
|
||||
* inline style modification during aScrollFrame's scroll event handler.
|
||||
@@ -60,8 +65,12 @@ public:
|
||||
* has been modified.
|
||||
* This notification is incomplete --- not all modifications to inline
|
||||
* style will trigger this.
|
||||
* aNewValue and aDOMCSSDecl are used to determine whether the property's
|
||||
* value has changed.
|
||||
*/
|
||||
static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSProperty aProperty);
|
||||
static void NotifyInlineStyleRuleModified(nsIFrame* aFrame, nsCSSProperty aProperty,
|
||||
const nsAString& aNewValue,
|
||||
nsDOMCSSDeclaration* aDOMCSSDecl);
|
||||
/**
|
||||
* Return true if aFrame's aProperty style should be considered as being animated
|
||||
* for pre-rendering.
|
||||
|
||||
@@ -140,8 +140,8 @@ GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType)
|
||||
{
|
||||
nsRect r;
|
||||
nsIFrame* f = nsSVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r);
|
||||
if (f) {
|
||||
// For SVG, the BoxType is ignored.
|
||||
if (f && f != *aFrame) {
|
||||
// For non-outer SVG frames, the BoxType is ignored.
|
||||
*aFrame = f;
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -60,8 +60,8 @@ public:
|
||||
using AccessibleCaretManager::UpdateCarets;
|
||||
using AccessibleCaretManager::HideCarets;
|
||||
using AccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent;
|
||||
using AccessibleCaretManager::sCaretsExtendedVisibility;
|
||||
using AccessibleCaretManager::sCaretsAlwaysTilt;
|
||||
using AccessibleCaretManager::sCaretsAlwaysShowWhenScrolling;
|
||||
|
||||
MockAccessibleCaretManager()
|
||||
: AccessibleCaretManager(nullptr)
|
||||
@@ -350,6 +350,11 @@ TEST_F(AccessibleCaretManagerTester, TestTypingAtEndOfInput)
|
||||
|
||||
TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionMode)
|
||||
{
|
||||
// Simulate B2G preference.
|
||||
AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
|
||||
|
||||
EXPECT_CALL(mManager, GetCaretMode())
|
||||
.WillRepeatedly(Return(CaretMode::Selection));
|
||||
|
||||
@@ -422,9 +427,13 @@ TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionMode)
|
||||
check.Call("scrollend2");
|
||||
}
|
||||
|
||||
TEST_F(AccessibleCaretManagerTester,
|
||||
TestScrollInSelectionModeWithExtendedVisibilityAndAlwaysTilt)
|
||||
TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionModeWithAlwaysTiltPref)
|
||||
{
|
||||
// Simulate Firefox Android preference.
|
||||
AutoRestore<bool> saveCaretsAlwaysTilt(
|
||||
MockAccessibleCaretManager::sCaretsAlwaysTilt);
|
||||
MockAccessibleCaretManager::sCaretsAlwaysTilt = true;
|
||||
|
||||
EXPECT_CALL(mManager, GetCaretMode())
|
||||
.WillRepeatedly(Return(CaretMode::Selection));
|
||||
|
||||
@@ -441,7 +450,7 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("updatecarets"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
CaretChangedReason::Visibilitychange));
|
||||
CaretChangedReason::Scroll));
|
||||
EXPECT_CALL(check, Call("scrollstart1"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
@@ -458,7 +467,7 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("scrollend1"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
CaretChangedReason::Visibilitychange));
|
||||
CaretChangedReason::Scroll));
|
||||
EXPECT_CALL(check, Call("scrollstart2"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
@@ -471,14 +480,6 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("scrollend2"));
|
||||
}
|
||||
|
||||
// Simulate Firefox Android preferences.
|
||||
AutoRestore<bool> saveCaretsExtendedVisibility(
|
||||
MockAccessibleCaretManager::sCaretsExtendedVisibility);
|
||||
MockAccessibleCaretManager::sCaretsExtendedVisibility = true;
|
||||
AutoRestore<bool> saveCaretsAlwaysTilt(
|
||||
MockAccessibleCaretManager::sCaretsAlwaysTilt);
|
||||
MockAccessibleCaretManager::sCaretsAlwaysTilt = true;
|
||||
|
||||
mManager.UpdateCarets();
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
|
||||
@@ -486,12 +487,12 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
|
||||
mManager.OnScrollStart();
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
|
||||
check.Call("scrollstart1");
|
||||
|
||||
mManager.OnReflow();
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
|
||||
check.Call("reflow1");
|
||||
|
||||
mManager.OnScrollEnd();
|
||||
@@ -500,12 +501,12 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
check.Call("scrollend1");
|
||||
|
||||
mManager.OnScrollStart();
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
|
||||
check.Call("scrollstart2");
|
||||
|
||||
mManager.OnReflow();
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
|
||||
EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
|
||||
EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
|
||||
check.Call("reflow2");
|
||||
|
||||
@@ -517,6 +518,11 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
|
||||
TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenLogicallyVisible)
|
||||
{
|
||||
// Simulate B2G preference.
|
||||
AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
|
||||
|
||||
EXPECT_CALL(mManager, GetCaretMode())
|
||||
.WillRepeatedly(Return(CaretMode::Cursor));
|
||||
|
||||
@@ -577,6 +583,11 @@ TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenLogicallyVisible)
|
||||
|
||||
TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenHidden)
|
||||
{
|
||||
// Simulate B2G preference.
|
||||
AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
|
||||
|
||||
EXPECT_CALL(mManager, GetCaretMode())
|
||||
.WillRepeatedly(Return(CaretMode::Cursor));
|
||||
|
||||
@@ -631,6 +642,11 @@ TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenHidden)
|
||||
|
||||
TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeOnEmptyContent)
|
||||
{
|
||||
// Simulate B2G preference.
|
||||
AutoRestore<bool> savesCaretsAlwaysShowWhenScrolling(
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling);
|
||||
MockAccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = false;
|
||||
|
||||
EXPECT_CALL(mManager, GetCaretMode())
|
||||
.WillRepeatedly(Return(CaretMode::Cursor));
|
||||
|
||||
@@ -700,8 +716,13 @@ TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeOnEmptyContent)
|
||||
}
|
||||
|
||||
TEST_F(AccessibleCaretManagerTester,
|
||||
TestScrollInCursorModeOnEmptyContentWithSpecialPreference)
|
||||
TestScrollInCursorModeWithCaretShownWhenLongTappingOnEmptyContentPref)
|
||||
{
|
||||
// Simulate Firefox Android preference.
|
||||
AutoRestore<bool> savesCaretShownWhenLongTappingOnEmptyContent(
|
||||
MockAccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent);
|
||||
MockAccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = true;
|
||||
|
||||
EXPECT_CALL(mManager, GetCaretMode())
|
||||
.WillRepeatedly(Return(CaretMode::Cursor));
|
||||
|
||||
@@ -721,7 +742,7 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("longtap updatecarets"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
CaretChangedReason::Visibilitychange));
|
||||
CaretChangedReason::Scroll));
|
||||
EXPECT_CALL(check, Call("longtap scrollstart1"));
|
||||
|
||||
EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
|
||||
@@ -731,7 +752,7 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("longtap scrollend1"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
CaretChangedReason::Visibilitychange));
|
||||
CaretChangedReason::Scroll));
|
||||
EXPECT_CALL(check, Call("longtap scrollstart2"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
@@ -739,7 +760,7 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("longtap scrollend2"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
CaretChangedReason::Visibilitychange));
|
||||
CaretChangedReason::Scroll));
|
||||
EXPECT_CALL(check, Call("longtap scrollstart3"));
|
||||
|
||||
EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
|
||||
@@ -747,10 +768,6 @@ TEST_F(AccessibleCaretManagerTester,
|
||||
EXPECT_CALL(check, Call("longtap scrollend3"));
|
||||
}
|
||||
|
||||
AutoRestore<bool> savePref(
|
||||
MockAccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent);
|
||||
MockAccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = true;
|
||||
|
||||
// Simulate a single tap on an empty input.
|
||||
mManager.FirstCaret().SetAppearance(Appearance::None);
|
||||
mManager.UpdateCarets();
|
||||
|
||||
@@ -190,6 +190,11 @@ LOCAL_INCLUDES += [
|
||||
'/view',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
|
||||
LOCAL_INCLUDES += [
|
||||
'/widget/android',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
|
||||
|
||||
@@ -208,6 +208,42 @@ class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
|
||||
|
||||
self.assertEqual(target_content, sel.selected_content)
|
||||
|
||||
@parameterized(_input_id, el_id=_input_id)
|
||||
@parameterized(_textarea_id, el_id=_textarea_id)
|
||||
@parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
|
||||
@parameterized(_contenteditable_id, el_id=_contenteditable_id)
|
||||
@parameterized(_content_id, el_id=_content_id)
|
||||
def test_drag_swappable_carets(self, el_id):
|
||||
self.open_test_html(self._selection_html)
|
||||
el = self.marionette.find_element(By.ID, el_id)
|
||||
sel = SelectionManager(el)
|
||||
original_content = sel.content
|
||||
words = original_content.split()
|
||||
self.assertTrue(len(words) >= 1, 'Expect at least one word in the content.')
|
||||
|
||||
target_content1 = words[0]
|
||||
target_content2 = original_content[len(words[0]):]
|
||||
|
||||
# Get the location of the carets at the end of the content for later
|
||||
# use.
|
||||
el.tap()
|
||||
sel.select_all()
|
||||
end_caret_x, end_caret_y = sel.second_caret_location()
|
||||
|
||||
self.long_press_on_word(el, 0)
|
||||
|
||||
# Drag the first caret to the end and back to where it was
|
||||
# immediately. The selection range should not be collapsed.
|
||||
caret1_x, caret1_y = sel.first_caret_location()
|
||||
self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y)\
|
||||
.flick(el, end_caret_x, end_caret_y, caret1_x, caret1_y).perform()
|
||||
self.assertEqual(target_content1, sel.selected_content)
|
||||
|
||||
# Drag the first caret to the end.
|
||||
caret1_x, caret1_y = sel.first_caret_location()
|
||||
self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).perform()
|
||||
self.assertEqual(target_content2, sel.selected_content)
|
||||
|
||||
@parameterized(_input_id, el_id=_input_id)
|
||||
@parameterized(_textarea_id, el_id=_textarea_id)
|
||||
@parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
|
||||
@@ -414,6 +450,29 @@ class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
|
||||
self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
|
||||
'4\nuser can select this 5\nuser')
|
||||
|
||||
def test_drag_swappable_caret_over_non_selectable_field(self):
|
||||
self.open_test_html(self._multiplerange_html)
|
||||
body = self.marionette.find_element(By.ID, 'bd')
|
||||
sel3 = self.marionette.find_element(By.ID, 'sel3')
|
||||
sel4 = self.marionette.find_element(By.ID, 'sel4')
|
||||
sel = SelectionManager(body)
|
||||
|
||||
self.long_press_on_word(sel4, 3)
|
||||
(end_caret1_x, end_caret1_y), (end_caret2_x, end_caret2_y) = sel.carets_location()
|
||||
|
||||
self.long_press_on_word(sel3, 3)
|
||||
(caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
|
||||
|
||||
# Drag the first caret down, which will across the second caret.
|
||||
self.actions.flick(body, caret1_x, caret1_y, end_caret1_x, end_caret1_y).perform()
|
||||
self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
|
||||
'3\nuser can select')
|
||||
|
||||
# The old second caret becomes the first caret. Drag it down again.
|
||||
self.actions.flick(body, caret2_x, caret2_y, end_caret2_x, end_caret2_y).perform()
|
||||
self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()),
|
||||
'this')
|
||||
|
||||
def test_drag_caret_to_beginning_of_a_line(self):
|
||||
'''Bug 1094056
|
||||
Test caret visibility when caret is dragged to beginning of a line
|
||||
|
||||
@@ -336,7 +336,7 @@ TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText
|
||||
</div>
|
||||
|
||||
<div id="svgContainer">
|
||||
<svg id="svg" style="width:200px; height:200px; background:lightgray;">
|
||||
<svg id="svg" style="width:200px; height:200px; background:lightgray; border:7px solid blue; padding:4px">
|
||||
<circle id="circle" cx="50" cy="50" r="20" fill="red" style="margin:20px; padding:10px; border:15px solid black"></circle>
|
||||
<g transform="scale(2)">
|
||||
<foreignObject x="50" y="20">
|
||||
@@ -660,19 +660,26 @@ function runTest() {
|
||||
var svgContainerX = svgContainer.getBoundingClientRect().left;
|
||||
var svgContainerY = svgContainer.getBoundingClientRect().top;
|
||||
checkQuadIsRect("circle", {},
|
||||
svgContainerX + 30, svgContainerY + 30, 40, 40);
|
||||
svgContainerX + 41, svgContainerY + 41, 40, 40);
|
||||
// Box types are ignored for SVG elements.
|
||||
checkQuadIsRect("circle", {box:"content"},
|
||||
svgContainerX + 30, svgContainerY + 30, 40, 40);
|
||||
svgContainerX + 41, svgContainerY + 41, 40, 40);
|
||||
checkQuadIsRect("circle", {box:"padding"},
|
||||
svgContainerX + 30, svgContainerY + 30, 40, 40);
|
||||
svgContainerX + 41, svgContainerY + 41, 40, 40);
|
||||
checkQuadIsRect("circle", {box:"margin"},
|
||||
svgContainerX + 30, svgContainerY + 30, 40, 40);
|
||||
svgContainerX + 41, svgContainerY + 41, 40, 40);
|
||||
checkQuadIsRect("d", {toStr:"circle"},
|
||||
dX - (svgContainerX + 30), dY - (svgContainerY + 30), dW, dH);
|
||||
dX - (svgContainerX + 41), dY - (svgContainerY + 41), dW, dH);
|
||||
// Test foreignObject inside an SVG transform.
|
||||
checkQuadIsRect("foreign", {},
|
||||
svgContainerX + 100, svgContainerY + 40, 200, 120);
|
||||
svgContainerX + 111, svgContainerY + 51, 200, 120);
|
||||
// Outer <svg> elements support padding and content boxes
|
||||
checkQuadIsRect("svg", {box:"border"},
|
||||
svgContainerX, svgContainerY, 222, 222);
|
||||
checkQuadIsRect("svg", {box:"padding"},
|
||||
svgContainerX + 7, svgContainerY + 7, 208, 208);
|
||||
checkQuadIsRect("svg", {box:"content"},
|
||||
svgContainerX + 11, svgContainerY + 11, 200, 200);
|
||||
|
||||
// XXX Test SVG text (probably broken; unclear what the best way is to handle it)
|
||||
|
||||
|
||||
@@ -263,7 +263,7 @@ static void Shutdown();
|
||||
#include "GMPService.h"
|
||||
|
||||
#include "mozilla/dom/PresentationDeviceManager.h"
|
||||
#include "mozilla/dom/PresentationSessionTransport.h"
|
||||
#include "mozilla/dom/PresentationTCPSessionTransport.h"
|
||||
|
||||
#include "mozilla/TextInputProcessor.h"
|
||||
|
||||
@@ -298,7 +298,7 @@ using mozilla::dom::NotificationTelemetryService;
|
||||
#define PRESENTATION_DEVICE_MANAGER_CID \
|
||||
{ 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } }
|
||||
|
||||
#define PRESENTATION_SESSION_TRANSPORT_CID \
|
||||
#define PRESENTATION_TCP_SESSION_TRANSPORT_CID \
|
||||
{ 0xc9d023f4, 0x6228, 0x4c07, { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } }
|
||||
|
||||
already_AddRefed<nsIPresentationService> NS_CreatePresentationService();
|
||||
@@ -405,7 +405,7 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeInputPortService,
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(InputPortData)
|
||||
NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService,
|
||||
NS_CreatePresentationService)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationSessionTransport)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationTCPSessionTransport)
|
||||
NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(NotificationTelemetryService, Init)
|
||||
|
||||
#ifndef MOZ_SIMPLEPUSH
|
||||
@@ -872,7 +872,7 @@ NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID);
|
||||
|
||||
NS_DEFINE_NAMED_CID(PRESENTATION_SERVICE_CID);
|
||||
NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID);
|
||||
NS_DEFINE_NAMED_CID(PRESENTATION_SESSION_TRANSPORT_CID);
|
||||
NS_DEFINE_NAMED_CID(PRESENTATION_TCP_SESSION_TRANSPORT_CID);
|
||||
|
||||
NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID);
|
||||
|
||||
@@ -1171,7 +1171,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = {
|
||||
{ &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor },
|
||||
{ &kPRESENTATION_SERVICE_CID, false, nullptr, nsIPresentationServiceConstructor },
|
||||
{ &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor },
|
||||
{ &kPRESENTATION_SESSION_TRANSPORT_CID, false, nullptr, PresentationSessionTransportConstructor },
|
||||
{ &kPRESENTATION_TCP_SESSION_TRANSPORT_CID, false, nullptr, PresentationTCPSessionTransportConstructor },
|
||||
{ &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor },
|
||||
{ &kFAKE_INPUTPORT_SERVICE_CID, false, nullptr, FakeInputPortServiceConstructor },
|
||||
{ &kINPUTPORT_DATA_CID, false, nullptr, InputPortDataConstructor },
|
||||
@@ -1340,7 +1340,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
|
||||
{ NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID },
|
||||
{ PRESENTATION_SERVICE_CONTRACTID, &kPRESENTATION_SERVICE_CID },
|
||||
{ PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID },
|
||||
{ PRESENTATION_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_SESSION_TRANSPORT_CID },
|
||||
{ PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_TCP_SESSION_TRANSPORT_CID },
|
||||
{ "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID },
|
||||
{ FAKE_INPUTPORT_SERVICE_CONTRACTID, &kFAKE_INPUTPORT_SERVICE_CID },
|
||||
{ INPUTPORT_DATA_CONTRACTID, &kINPUTPORT_DATA_CID },
|
||||
|
||||
@@ -1508,6 +1508,10 @@ void
|
||||
nsPrintEngine::FirePrintingErrorEvent(nsresult aPrintError)
|
||||
{
|
||||
nsCOMPtr<nsIContentViewer> cv = do_QueryInterface(mDocViewerPrint);
|
||||
if (NS_WARN_IF(!cv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIDocument> doc = cv->GetDocument();
|
||||
RefPtr<CustomEvent> event =
|
||||
NS_NewDOMCustomEvent(doc, nullptr, nullptr);
|
||||
|
||||
@@ -6,3 +6,4 @@
|
||||
== at-rule-error-handling-media-1.html at-rule-error-handling-ref.html
|
||||
== invalid-font-face-descriptor-1.html invalid-font-face-descriptor-1-ref.html
|
||||
== two-dash-identifiers.html two-dash-identifiers-ref.html
|
||||
== supports-moz-bool-pref.html supports-moz-bool-pref-ref.html
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user