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:
2024-08-07 16:43:22 +08:00
parent 9f3e1414a7
commit 96a33978d6
211 changed files with 5838 additions and 1610 deletions
+6 -1
View File
@@ -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);
+2
View File
@@ -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);
});
+2
View File
@@ -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">
+46 -8
View File
@@ -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.
+16
View File
@@ -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, "&amp;");
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,
});
+13 -4
View File
@@ -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
*/
+18 -1
View File
@@ -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 };
};
+55 -8
View File
@@ -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;
}
+4 -2
View File
@@ -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;
+4 -19
View File
@@ -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}
+26
View File
@@ -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
+8 -1
View File
@@ -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);
}
+3
View File
@@ -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);
+76 -12
View File
@@ -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)
+206 -86
View File
@@ -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.");
+30 -5
View File
@@ -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
@@ -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());
@@ -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
+1
View File
@@ -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);
};
+2 -1
View File
@@ -17,12 +17,13 @@ struct StartSessionRequest
nsString url;
nsString sessionId;
nsString origin;
nsString deviceId;
};
struct SendSessionMessageRequest
{
nsString sessionId;
InputStreamParams data;
nsString data;
};
struct CloseSessionRequest
+35 -17
View File
@@ -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);
}
+10 -11
View File
@@ -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);
}
+9 -2
View File
@@ -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 });
});
}
+15 -8
View File
@@ -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>
@@ -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);
});
@@ -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);
});
@@ -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);
});
@@ -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);
@@ -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);
});
@@ -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);
});
@@ -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);
});
@@ -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();
+2 -1
View File
@@ -10,7 +10,8 @@ enum CaretChangedReason {
"longpressonemptycontent",
"taponcaret",
"presscaret",
"releasecaret"
"releasecaret",
"scroll"
};
dictionary CaretStateChangedEventInit : EventInit {
+1 -1
View File
@@ -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]
+20
View File
@@ -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)) {
+1
View File
@@ -6,3 +6,4 @@ skip-if = buildapp == 'b2g'
[test_bug434998.xul]
[test_bug678842.html]
[test_bug717433.html]
[test_bug1219928.html]
+72
View File
@@ -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>
+9 -10
View File
@@ -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);
};
+9 -17
View File
@@ -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
-2
View File
@@ -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}
+8
View File
@@ -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)
{
+6
View File
@@ -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.
///
+7 -2
View File
@@ -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);
}
}
+18
View File
@@ -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;
}
+18 -20
View File
@@ -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) {
-17
View File
@@ -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
{
-2
View File
@@ -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
+202 -112
View File
@@ -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.
//
+39 -17
View File
@@ -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;
};
+17 -5
View File
@@ -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()) {
+11 -2
View File
@@ -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.
+2 -2
View File
@@ -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();
+5
View File
@@ -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)
+6 -6
View File
@@ -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 },
+4
View File
@@ -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);
+1
View File
@@ -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