mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-08 12:37:37 +00:00
Merge m-c to b2g-inbound a=merge
This commit is contained in:
commit
295f54f807
@ -92,7 +92,7 @@ pref("network.buffer.cache.count", 24);
|
||||
pref("network.buffer.cache.size", 16384);
|
||||
|
||||
// predictive actions
|
||||
pref("network.predictor.enable", false); // disabled on b2g
|
||||
pref("network.predictor.enabled", false); // disabled on b2g
|
||||
pref("network.predictor.max-db-size", 2097152); // bytes
|
||||
pref("network.predictor.preserve", 50); // percentage of predictor data to keep when cleaning up
|
||||
|
||||
@ -1068,6 +1068,9 @@ pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
|
||||
pref("dom.mapped_arraybuffer.enabled", true);
|
||||
#endif
|
||||
|
||||
// BroadcastChannel API
|
||||
pref("dom.broadcastChannel.enabled", true);
|
||||
|
||||
// UDPSocket API
|
||||
pref("dom.udpsocket.enabled", true);
|
||||
|
||||
|
@ -2562,7 +2562,7 @@ let BrowserOnClick = {
|
||||
let mm = window.messageManager;
|
||||
mm.addMessageListener("Browser:CertExceptionError", this);
|
||||
mm.addMessageListener("Browser:SiteBlockedError", this);
|
||||
mm.addMessageListener("Browser:NetworkError", this);
|
||||
mm.addMessageListener("Browser:EnableOnlineMode", this);
|
||||
mm.addMessageListener("Browser:SendSSLErrorReport", this);
|
||||
mm.addMessageListener("Browser:SetSSLErrorReportAuto", this);
|
||||
},
|
||||
@ -2571,7 +2571,7 @@ let BrowserOnClick = {
|
||||
let mm = window.messageManager;
|
||||
mm.removeMessageListener("Browser:CertExceptionError", this);
|
||||
mm.removeMessageListener("Browser:SiteBlockedError", this);
|
||||
mm.removeMessageListener("Browser:NetworkError", this);
|
||||
mm.removeMessageListener("Browser:EnableOnlineMode", this);
|
||||
mm.removeMessageListener("Browser:SendSSLErrorReport", this);
|
||||
mm.removeMessageListener("Browser:SetSSLErrorReportAuto", this);
|
||||
},
|
||||
@ -2605,9 +2605,12 @@ let BrowserOnClick = {
|
||||
this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
|
||||
msg.data.isTopFrame, msg.data.location);
|
||||
break;
|
||||
case "Browser:NetworkError":
|
||||
// Reset network state, the error page will refresh on its own.
|
||||
Services.io.offline = false;
|
||||
case "Browser:EnableOnlineMode":
|
||||
if (Services.io.offline) {
|
||||
// Reset network state and refresh the page.
|
||||
Services.io.offline = false;
|
||||
msg.target.reload();
|
||||
}
|
||||
break;
|
||||
case "Browser:SendSSLErrorReport":
|
||||
this.onSSLErrorReport(msg.target, msg.data.elementId,
|
||||
|
@ -568,7 +568,8 @@ let ClickEventHandler = {
|
||||
this.onAboutBlocked(originalTarget, ownerDoc);
|
||||
return;
|
||||
} else if (ownerDoc.documentURI.startsWith("about:neterror")) {
|
||||
this.onAboutNetError(originalTarget, ownerDoc);
|
||||
this.onAboutNetError(event, ownerDoc.documentURI);
|
||||
return;
|
||||
}
|
||||
|
||||
let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
|
||||
@ -635,12 +636,18 @@ let ClickEventHandler = {
|
||||
});
|
||||
},
|
||||
|
||||
onAboutNetError: function (targetElement, ownerDoc) {
|
||||
let elmId = targetElement.getAttribute("id");
|
||||
if (elmId != "errorTryAgain" || !/e=netOffline/.test(ownerDoc.documentURI)) {
|
||||
onAboutNetError: function (event, documentURI) {
|
||||
let elmId = event.originalTarget.getAttribute("id");
|
||||
if (elmId != "errorTryAgain" || !/e=netOffline/.test(documentURI)) {
|
||||
return;
|
||||
}
|
||||
sendSyncMessage("Browser:NetworkError", {});
|
||||
// browser front end will handle clearing offline mode and refreshing
|
||||
// the page *if* we're in offline mode now. Otherwise let the error page
|
||||
// handle the click.
|
||||
if (Services.io.offline) {
|
||||
event.preventDefault();
|
||||
sendAsyncMessage("Browser:EnableOnlineMode", {});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -56,12 +56,12 @@ function checkPage() {
|
||||
// the actual example.com.
|
||||
Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
|
||||
|
||||
Services.obs.addObserver(function observer(aSubject, aTopic) {
|
||||
ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
|
||||
"online.");
|
||||
finish();
|
||||
}, "network:offline-status-changed", false);
|
||||
gBrowser.contentDocument.getElementById("errorTryAgain").click();
|
||||
|
||||
ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
|
||||
"online.");
|
||||
|
||||
finish();
|
||||
}
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
|
@ -37,8 +37,8 @@ function runTests() {
|
||||
yield setLinks("-1");
|
||||
|
||||
// Test with enhanced = false
|
||||
NewTabUtils.allPages.enhanced = false;
|
||||
yield addNewTabPageTab();
|
||||
yield customizeNewTabPage("classic");
|
||||
let {type, enhanced, title} = getData(0);
|
||||
is(type, "organic", "directory link is organic");
|
||||
isnot(enhanced, "", "directory link has enhanced image");
|
||||
@ -47,8 +47,8 @@ function runTests() {
|
||||
is(getData(1), null, "history link pushed out by directory link");
|
||||
|
||||
// Test with enhanced = true
|
||||
NewTabUtils.allPages.enhanced = true;
|
||||
yield addNewTabPageTab();
|
||||
yield customizeNewTabPage("enhanced");
|
||||
({type, enhanced, title} = getData(0));
|
||||
is(type, "organic", "directory link is still organic");
|
||||
isnot(enhanced, "", "directory link still has enhanced image");
|
||||
@ -67,12 +67,15 @@ function runTests() {
|
||||
is(getData(1), null, "directory link pushed out by pinned history link");
|
||||
|
||||
// Test pinned link with enhanced = false
|
||||
NewTabUtils.allPages.enhanced = false;
|
||||
yield addNewTabPageTab();
|
||||
yield customizeNewTabPage("classic");
|
||||
({type, enhanced, title} = getData(0));
|
||||
isnot(type, "enhanced", "history link is not enhanced");
|
||||
is(enhanced, "", "history link has no enhanced image");
|
||||
is(title, "site#-1");
|
||||
|
||||
is(getData(1), null, "directory link still pushed out by pinned history link");
|
||||
|
||||
ok(getContentDocument().getElementById("newtab-intro-what"),
|
||||
"'What is this page?' link exists");
|
||||
}
|
||||
|
@ -684,3 +684,34 @@ function whenSearchInitDone() {
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the newtab customization option and waits for the panel to open and close
|
||||
*
|
||||
* @param {string} aTheme
|
||||
* Can be any of("blank"|"classic"|"enhanced")
|
||||
*/
|
||||
function customizeNewTabPage(aTheme) {
|
||||
let document = getContentDocument();
|
||||
let panel = document.getElementById("newtab-customize-panel");
|
||||
let customizeButton = document.getElementById("newtab-customize-button");
|
||||
|
||||
// Attache onShown the listener on panel
|
||||
panel.addEventListener("popupshown", function onShown() {
|
||||
panel.removeEventListener("popupshown", onShown);
|
||||
|
||||
// Get the element for the specific option and click on it,
|
||||
// then trigger an escape to close the panel
|
||||
document.getElementById("newtab-customize-" + aTheme).click();
|
||||
executeSoon(() => { panel.hidePopup(); });
|
||||
});
|
||||
|
||||
// Attache the listener for panel closing, this will resolve the promise
|
||||
panel.addEventListener("popuphidden", function onHidden() {
|
||||
panel.removeEventListener("popuphidden", onHidden);
|
||||
executeSoon(TestRunner.next);
|
||||
});
|
||||
|
||||
// Click on the customize button to display the panel
|
||||
customizeButton.click();
|
||||
}
|
||||
|
@ -193,21 +193,26 @@ PlayerWidget.prototype = {
|
||||
});
|
||||
let titleHTML = "";
|
||||
|
||||
// Name
|
||||
// Name.
|
||||
if (state.name) {
|
||||
// Css animations have names
|
||||
// Css animations have names.
|
||||
titleHTML += L10N.getStr("player.animationNameLabel");
|
||||
titleHTML += "<strong>" + state.name + "</strong>";
|
||||
} else {
|
||||
// Css transitions don't
|
||||
// Css transitions don't.
|
||||
titleHTML += L10N.getStr("player.transitionNameLabel");
|
||||
}
|
||||
|
||||
// Duration and iteration count
|
||||
// Duration, delay and iteration count.
|
||||
titleHTML += "<span class='meta-data'>";
|
||||
titleHTML += L10N.getStr("player.animationDurationLabel");
|
||||
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime(state.duration)) + "</strong>";
|
||||
if (state.delay) {
|
||||
titleHTML += L10N.getStr("player.animationDelayLabel");
|
||||
titleHTML += "<strong>" + L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime(state.delay)) + "</strong>";
|
||||
}
|
||||
titleHTML += L10N.getStr("player.animationIterationCountLabel");
|
||||
let count = state.iterationCount || L10N.getStr("player.infiniteIterationCount");
|
||||
titleHTML += "<strong>" + count + "</strong>";
|
||||
@ -215,7 +220,7 @@ PlayerWidget.prototype = {
|
||||
|
||||
titleEl.innerHTML = titleHTML;
|
||||
|
||||
// Timeline widget
|
||||
// Timeline widget.
|
||||
let timelineEl = createNode({
|
||||
parent: this.el,
|
||||
attributes: {
|
||||
@ -223,7 +228,7 @@ PlayerWidget.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
// Playback control buttons container
|
||||
// Playback control buttons container.
|
||||
let playbackControlsEl = createNode({
|
||||
parent: timelineEl,
|
||||
attributes: {
|
||||
@ -241,7 +246,7 @@ PlayerWidget.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
// Sliders container
|
||||
// Sliders container.
|
||||
let slidersContainerEl = createNode({
|
||||
parent: timelineEl,
|
||||
attributes: {
|
||||
@ -249,9 +254,9 @@ PlayerWidget.prototype = {
|
||||
}
|
||||
});
|
||||
|
||||
let max = state.duration; // Infinite iterations
|
||||
let max = state.duration; // Infinite iterations.
|
||||
if (state.iterationCount) {
|
||||
// Finite iterations
|
||||
// Finite iterations.
|
||||
max = state.iterationCount * state.duration;
|
||||
}
|
||||
|
||||
@ -267,6 +272,7 @@ PlayerWidget.prototype = {
|
||||
"min": "0",
|
||||
"max": max,
|
||||
"step": "10",
|
||||
"value": "0",
|
||||
// The currentTime isn't settable yet, so disable the timeline slider
|
||||
"disabled": "true"
|
||||
}
|
||||
@ -280,7 +286,7 @@ PlayerWidget.prototype = {
|
||||
}
|
||||
});
|
||||
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime());
|
||||
this.getFormattedTime(0));
|
||||
|
||||
this.containerEl.appendChild(this.el);
|
||||
},
|
||||
@ -290,14 +296,11 @@ PlayerWidget.prototype = {
|
||||
* @param {Number} time Defaults to the player's currentTime.
|
||||
* @return {String} The formatted time, e.g. "10.55"
|
||||
*/
|
||||
getFormattedTime: function(time=this.player.state.currentTime) {
|
||||
let str = time/1000 + "";
|
||||
str = str.split(".");
|
||||
if (str.length === 1) {
|
||||
return str[0] + ".00";
|
||||
} else {
|
||||
return str[0] + "." + str[1].substring(0, 2);
|
||||
}
|
||||
getFormattedTime: function(time) {
|
||||
return (time/1000).toLocaleString(undefined, {
|
||||
minimumFractionDigits: 2,
|
||||
maximumFractionDigits: 2
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -390,6 +393,12 @@ PlayerWidget.prototype = {
|
||||
displayTime: function(time) {
|
||||
let state = this.player.state;
|
||||
|
||||
// If the animation is delayed, don't start displaying the time until the
|
||||
// delay has passed.
|
||||
if (state.delay) {
|
||||
time = Math.max(0, time - state.delay);
|
||||
}
|
||||
|
||||
this.timeDisplayEl.textContent = L10N.getFormatStr("player.timeLabel",
|
||||
this.getFormattedTime(time));
|
||||
if (!state.iterationCount && time !== state.duration) {
|
||||
|
@ -11,8 +11,10 @@ support-files =
|
||||
[browser_animation_play_pause_button.js]
|
||||
[browser_animation_playerFronts_are_refreshed.js]
|
||||
[browser_animation_playerWidgets_destroy.js]
|
||||
[browser_animation_playerWidgets_meta_data.js]
|
||||
[browser_animation_refresh_when_active.js]
|
||||
[browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
|
||||
[browser_animation_shows_player_on_valid_node.js]
|
||||
[browser_animation_timeline_animates.js]
|
||||
[browser_animation_ui_updates_when_animation_changes.js]
|
||||
[browser_animation_timeline_waits_for_delay.js]
|
||||
[browser_animation_ui_updates_when_animation_changes.js]
|
@ -0,0 +1,49 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that player widgets show the right player meta-data (name, duration,
|
||||
// iteration count, delay).
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
|
||||
let {inspector, panel} = yield openAnimationInspector();
|
||||
|
||||
info("Select the simple animated node");
|
||||
yield selectNode(".animated", inspector);
|
||||
|
||||
let titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
|
||||
ok(titleEl,
|
||||
"The player widget has a title element, where meta-data should be displayed");
|
||||
|
||||
let nameEl = titleEl.querySelector("strong");
|
||||
ok(nameEl, "The first <strong> tag was retrieved, it should contain the name");
|
||||
is(nameEl.textContent, "simple-animation", "The animation name is correct");
|
||||
|
||||
let metaDataEl = titleEl.querySelector(".meta-data");
|
||||
ok(metaDataEl, "The meta-data element exists");
|
||||
|
||||
let metaDataEls = metaDataEl.querySelectorAll("strong");
|
||||
is(metaDataEls.length, 2, "2 meta-data elements were found");
|
||||
is(metaDataEls[0].textContent, "2.00s",
|
||||
"The first meta-data is the duration, and is correct");
|
||||
|
||||
info("Select the node with the delayed animation");
|
||||
yield selectNode(".delayed", inspector);
|
||||
|
||||
titleEl = panel.playerWidgets[0].el.querySelector(".animation-title");
|
||||
nameEl = titleEl.querySelector("strong");
|
||||
is(nameEl.textContent, "simple-animation", "The animation name is correct");
|
||||
|
||||
metaDataEls = titleEl.querySelectorAll(".meta-data strong");
|
||||
is(metaDataEls.length, 3,
|
||||
"3 meta-data elements were found for the delayed animation");
|
||||
is(metaDataEls[0].textContent, "3.00s",
|
||||
"The first meta-data is the duration, and is correct");
|
||||
is(metaDataEls[1].textContent, "60.00s",
|
||||
"The second meta-data is the delay, and is correct");
|
||||
is(metaDataEls[2].textContent, "10",
|
||||
"The third meta-data is the iteration count, and is correct");
|
||||
});
|
@ -0,0 +1,24 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test that the currentTime timeline doesn't move if the animation is currently
|
||||
// waiting for an animation-delay.
|
||||
|
||||
add_task(function*() {
|
||||
yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
|
||||
let {inspector, panel} = yield openAnimationInspector();
|
||||
|
||||
info("Select the delayed animation node");
|
||||
yield selectNode(".delayed", inspector);
|
||||
|
||||
let widget = panel.playerWidgets[0];
|
||||
|
||||
let timeline = widget.currentTimeEl;
|
||||
is(timeline.value, 0, "The timeline is at 0 since the animation hasn't started");
|
||||
|
||||
let timeLabel = widget.timeDisplayEl;
|
||||
is(timeLabel.textContent, "0.00s", "The current time is 0");
|
||||
});
|
@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
.ball {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: #f06;
|
||||
|
||||
@ -13,25 +13,33 @@
|
||||
}
|
||||
|
||||
.still {
|
||||
top: 50px;
|
||||
left: 50px;
|
||||
top: 0;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.animated {
|
||||
top: 200px;
|
||||
left: 200px;
|
||||
top: 100px;
|
||||
left: 10px;
|
||||
|
||||
animation: simple-animation 2s infinite alternate;
|
||||
}
|
||||
|
||||
.multi {
|
||||
top: 100px;
|
||||
left: 400px;
|
||||
top: 200px;
|
||||
left: 10px;
|
||||
|
||||
animation: simple-animation 2s infinite alternate,
|
||||
other-animation 5s infinite alternate;
|
||||
}
|
||||
|
||||
.delayed {
|
||||
top: 300px;
|
||||
left: 10px;
|
||||
background: rebeccapurple;
|
||||
|
||||
animation: simple-animation 3s 60s 10;
|
||||
}
|
||||
|
||||
@keyframes simple-animation {
|
||||
100% {
|
||||
transform: translateX(300px);
|
||||
@ -50,5 +58,6 @@
|
||||
<div class="ball still"></div>
|
||||
<div class="ball animated"></div>
|
||||
<div class="ball multi"></div>
|
||||
<div class="ball delayed"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -30,6 +30,7 @@ support-files =
|
||||
[browser_canvas-frontend-img-thumbnails-01.js]
|
||||
[browser_canvas-frontend-img-thumbnails-02.js]
|
||||
[browser_canvas-frontend-open.js]
|
||||
skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in e10s mode
|
||||
[browser_canvas-frontend-record-01.js]
|
||||
[browser_canvas-frontend-record-02.js]
|
||||
[browser_canvas-frontend-record-03.js]
|
||||
|
@ -118,7 +118,7 @@ skip-if = e10s && debug
|
||||
[browser_dbg_auto-pretty-print-02.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_bfcache.js]
|
||||
skip-if = e10s # TODO
|
||||
skip-if = e10s || true # bug 1113935
|
||||
[browser_dbg_blackboxing-01.js]
|
||||
skip-if = e10s && debug
|
||||
[browser_dbg_blackboxing-02.js]
|
||||
|
@ -12,24 +12,19 @@ const TAB_URL_2 = EXAMPLE_URL + "doc_recursion-stack.html";
|
||||
let gTab, gDebuggee, gPanel, gDebugger;
|
||||
let gSources;
|
||||
|
||||
function test() {
|
||||
initDebugger(TAB_URL_1).then(([aTab, aDebuggee, aPanel]) => {
|
||||
gTab = aTab;
|
||||
gDebuggee = aDebuggee;
|
||||
gPanel = aPanel;
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
const test = Task.async(function* () {
|
||||
info("Starting browser_dbg_bfcache.js's `test`.");
|
||||
|
||||
testFirstPage()
|
||||
.then(testLocationChange)
|
||||
.then(testBack)
|
||||
.then(testForward)
|
||||
.then(() => closeDebuggerAndFinish(gPanel))
|
||||
.then(null, aError => {
|
||||
ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
|
||||
});
|
||||
});
|
||||
}
|
||||
([gTab, gDebuggee, gPanel]) = yield initDebugger(TAB_URL_1);
|
||||
gDebugger = gPanel.panelWin;
|
||||
gSources = gDebugger.DebuggerView.Sources;
|
||||
|
||||
yield testFirstPage();
|
||||
yield testLocationChange();
|
||||
yield testBack();
|
||||
yield testForward();
|
||||
return closeDebuggerAndFinish(gPanel);
|
||||
});
|
||||
|
||||
function testFirstPage() {
|
||||
info("Testing first page.");
|
||||
@ -38,33 +33,35 @@ function testFirstPage() {
|
||||
// this function to return first.
|
||||
executeSoon(() => gDebuggee.firstCall());
|
||||
|
||||
return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(() => {
|
||||
validateFirstPage();
|
||||
});
|
||||
return waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
|
||||
.then(validateFirstPage);
|
||||
}
|
||||
|
||||
function testLocationChange() {
|
||||
info("Navigating to a different page.");
|
||||
|
||||
return navigateActiveTabTo(gPanel, TAB_URL_2, gDebugger.EVENTS.SOURCES_ADDED).then(() => {
|
||||
validateSecondPage();
|
||||
});
|
||||
return navigateActiveTabTo(gPanel,
|
||||
TAB_URL_2,
|
||||
gDebugger.EVENTS.SOURCES_ADDED)
|
||||
.then(validateSecondPage);
|
||||
}
|
||||
|
||||
function testBack() {
|
||||
info("Going back.");
|
||||
|
||||
return navigateActiveTabInHistory(gPanel, "back", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
|
||||
validateFirstPage();
|
||||
});
|
||||
return navigateActiveTabInHistory(gPanel,
|
||||
"back",
|
||||
gDebugger.EVENTS.SOURCES_ADDED)
|
||||
.then(validateFirstPage);
|
||||
}
|
||||
|
||||
function testForward() {
|
||||
info("Going forward.");
|
||||
|
||||
return navigateActiveTabInHistory(gPanel, "forward", gDebugger.EVENTS.SOURCES_ADDED).then(() => {
|
||||
validateSecondPage();
|
||||
});
|
||||
return navigateActiveTabInHistory(gPanel,
|
||||
"forward",
|
||||
gDebugger.EVENTS.SOURCES_ADDED)
|
||||
.then(validateSecondPage);
|
||||
}
|
||||
|
||||
function validateFirstPage() {
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
const {Cu} = require("chrome");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
@ -18,6 +19,23 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
/**
|
||||
* ToolSidebar provides methods to register tabs in the sidebar.
|
||||
* It's assumed that the sidebar contains a xul:tabbox.
|
||||
* Typically, you'll want the tabbox parameter to be a XUL tabbox like this:
|
||||
*
|
||||
* <tabbox id="inspector-sidebar" handleCtrlTab="false" class="devtools-sidebar-tabs">
|
||||
* <tabs/>
|
||||
* <tabpanels flex="1"/>
|
||||
* </tabbox>
|
||||
*
|
||||
* The ToolSidebar API has a method to add new tabs, so the tabs and tabpanels
|
||||
* nodes can be empty. But they can also already contain items before the
|
||||
* ToolSidebar is created.
|
||||
*
|
||||
* Tabs added through the addTab method are only identified by an ID and a URL
|
||||
* which is used as the href of an iframe node that is inserted in the newly
|
||||
* created tabpanel.
|
||||
* Tabs already present before the ToolSidebar is created may contain anything.
|
||||
* However, these tabs must have ID attributes if it is required for the various
|
||||
* methods that accept an ID as argument to work here.
|
||||
*
|
||||
* @param {Node} tabbox
|
||||
* <tabbox> node;
|
||||
@ -25,38 +43,168 @@ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
||||
* Related ToolPanel instance;
|
||||
* @param {String} uid
|
||||
* Unique ID
|
||||
* @param {Boolean} showTabstripe
|
||||
* Show the tabs.
|
||||
* @param {Object} options
|
||||
* - hideTabstripe: Should the tabs be hidden. Defaults to false
|
||||
* - showAllTabsMenu: Should a drop-down menu be displayed in case tabs
|
||||
* become hidden. Defaults to false.
|
||||
* - disableTelemetry: By default, switching tabs on and off in the sidebar
|
||||
* will record tool usage in telemetry, pass this option to true to avoid it.
|
||||
*
|
||||
* Events raised:
|
||||
* - new-tab-registered : After a tab has been added via addTab. The tab ID
|
||||
* is passed with the event. This however, is raised before the tab iframe
|
||||
* is fully loaded.
|
||||
* - <tabid>-ready : After the tab iframe has been loaded
|
||||
* - <tabid>-selected : After tab <tabid> was selected
|
||||
* - select : Same as above, but for any tab, the ID is passed with the event
|
||||
* - <tabid>-unselected : After tab <tabid> is unselected
|
||||
*/
|
||||
function ToolSidebar(tabbox, panel, uid, showTabstripe=true)
|
||||
{
|
||||
function ToolSidebar(tabbox, panel, uid, options={}) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._tabbox = tabbox;
|
||||
this._uid = uid;
|
||||
this._panelDoc = this._tabbox.ownerDocument;
|
||||
this._toolPanel = panel;
|
||||
this._options = options;
|
||||
|
||||
this._onTabBoxOverflow = this._onTabBoxOverflow.bind(this);
|
||||
this._onTabBoxUnderflow = this._onTabBoxUnderflow.bind(this);
|
||||
|
||||
try {
|
||||
this._width = Services.prefs.getIntPref("devtools.toolsidebar-width." + this._uid);
|
||||
} catch(e) {}
|
||||
|
||||
this._telemetry = new Telemetry();
|
||||
if (!options.disableTelemetry) {
|
||||
this._telemetry = new Telemetry();
|
||||
}
|
||||
|
||||
this._tabbox.tabpanels.addEventListener("select", this, true);
|
||||
|
||||
this._tabs = new Map();
|
||||
|
||||
if (!showTabstripe) {
|
||||
// Check for existing tabs in the DOM and add them.
|
||||
this.addExistingTabs();
|
||||
|
||||
if (this._options.hideTabstripe) {
|
||||
this._tabbox.setAttribute("hidetabs", "true");
|
||||
}
|
||||
|
||||
if (this._options.showAllTabsMenu) {
|
||||
this.addAllTabsMenu();
|
||||
}
|
||||
|
||||
this._toolPanel.emit("sidebar-created", this);
|
||||
}
|
||||
|
||||
exports.ToolSidebar = ToolSidebar;
|
||||
|
||||
ToolSidebar.prototype = {
|
||||
TAB_ID_PREFIX: "sidebar-tab-",
|
||||
|
||||
TABPANEL_ID_PREFIX: "sidebar-panel-",
|
||||
|
||||
/**
|
||||
* Add a "…" button at the end of the tabstripe that toggles a dropdown menu
|
||||
* containing the list of all tabs if any become hidden due to lack of room.
|
||||
*
|
||||
* If the ToolSidebar was created with the "showAllTabsMenu" option set to
|
||||
* true, this is already done automatically. If not, you may call this
|
||||
* function at any time to add the menu.
|
||||
*/
|
||||
addAllTabsMenu: function() {
|
||||
if (this._allTabsBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabs = this._tabbox.tabs;
|
||||
|
||||
// Create a toolbar and insert it first in the tabbox
|
||||
let allTabsToolbar = this._panelDoc.createElementNS(XULNS, "toolbar");
|
||||
this._tabbox.insertBefore(allTabsToolbar, tabs);
|
||||
|
||||
// Move the tabs inside and make them flex
|
||||
allTabsToolbar.appendChild(tabs);
|
||||
tabs.setAttribute("flex", "1");
|
||||
|
||||
// Create the dropdown menu next to the tabs
|
||||
this._allTabsBtn = this._panelDoc.createElementNS(XULNS, "toolbarbutton");
|
||||
this._allTabsBtn.setAttribute("class", "devtools-sidebar-alltabs");
|
||||
this._allTabsBtn.setAttribute("type", "menu");
|
||||
this._allTabsBtn.setAttribute("label", l10n("sidebar.showAllTabs.label"));
|
||||
this._allTabsBtn.setAttribute("tooltiptext", l10n("sidebar.showAllTabs.tooltip"));
|
||||
this._allTabsBtn.setAttribute("hidden", "true");
|
||||
allTabsToolbar.appendChild(this._allTabsBtn);
|
||||
|
||||
let menuPopup = this._panelDoc.createElementNS(XULNS, "menupopup");
|
||||
this._allTabsBtn.appendChild(menuPopup);
|
||||
|
||||
// Listening to tabs overflow event to toggle the alltabs button
|
||||
tabs.addEventListener("overflow", this._onTabBoxOverflow, false);
|
||||
tabs.addEventListener("underflow", this._onTabBoxUnderflow, false);
|
||||
|
||||
// Add menuitems to the alltabs menu if there are already tabs in the
|
||||
// sidebar
|
||||
for (let [id, tab] of this._tabs) {
|
||||
this._addItemToAllTabsMenu(id, tab, tab.hasAttribute("selected"));
|
||||
}
|
||||
},
|
||||
|
||||
removeAllTabsMenu: function() {
|
||||
if (!this._allTabsBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabs = this._tabbox.tabs;
|
||||
|
||||
tabs.removeEventListener("overflow", this._onTabBoxOverflow, false);
|
||||
tabs.removeEventListener("underflow", this._onTabBoxUnderflow, false);
|
||||
|
||||
// Moving back the tabs as a first child of the tabbox
|
||||
this._tabbox.insertBefore(tabs, this._tabbox.tabpanels);
|
||||
this._tabbox.querySelector("toolbar").remove();
|
||||
|
||||
this._allTabsBtn = null;
|
||||
},
|
||||
|
||||
_onTabBoxOverflow: function() {
|
||||
this._allTabsBtn.removeAttribute("hidden");
|
||||
},
|
||||
|
||||
_onTabBoxUnderflow: function() {
|
||||
this._allTabsBtn.setAttribute("hidden", "true");
|
||||
},
|
||||
|
||||
/**
|
||||
* Add an item in the allTabs menu for a given tab.
|
||||
*/
|
||||
_addItemToAllTabsMenu: function(id, tab, selected=false) {
|
||||
if (!this._allTabsBtn) {
|
||||
return;
|
||||
}
|
||||
|
||||
let item = this._panelDoc.createElementNS(XULNS, "menuitem");
|
||||
item.setAttribute("id", "sidebar-alltabs-item-" + id);
|
||||
item.setAttribute("label", tab.getAttribute("label"));
|
||||
item.setAttribute("type", "checkbox");
|
||||
if (selected) {
|
||||
item.setAttribute("checked", true);
|
||||
}
|
||||
// The auto-checking of menuitems in this menu doesn't work, so let's do
|
||||
// it manually
|
||||
item.setAttribute("autocheck", false);
|
||||
|
||||
this._allTabsBtn.querySelector("menupopup").appendChild(item);
|
||||
|
||||
item.addEventListener("click", () => {
|
||||
this._tabbox.selectedTab = tab;
|
||||
}, false);
|
||||
|
||||
tab.allTabsMenuItem = item;
|
||||
|
||||
return item;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a tab. A tab is a document.
|
||||
* The document must have a title, which will be used as the name of the tab.
|
||||
@ -64,22 +212,31 @@ ToolSidebar.prototype = {
|
||||
* @param {string} tab uniq id
|
||||
* @param {string} url
|
||||
*/
|
||||
addTab: function ToolSidebar_addTab(id, url, selected=false) {
|
||||
addTab: function(id, url, selected=false) {
|
||||
let iframe = this._panelDoc.createElementNS(XULNS, "iframe");
|
||||
iframe.className = "iframe-" + id;
|
||||
iframe.setAttribute("flex", "1");
|
||||
iframe.setAttribute("src", url);
|
||||
iframe.tooltip = "aHTMLTooltip";
|
||||
|
||||
let tab = this._tabbox.tabs.appendItem();
|
||||
// Creating the tab and adding it to the tabbox
|
||||
let tab = this._panelDoc.createElementNS(XULNS, "tab");
|
||||
this._tabbox.tabs.appendChild(tab);
|
||||
tab.setAttribute("label", ""); // Avoid showing "undefined" while the tab is loading
|
||||
tab.setAttribute("id", "sidebar-tab-" + id);
|
||||
tab.setAttribute("id", this.TAB_ID_PREFIX + id);
|
||||
|
||||
// Add the tab to the allTabs menu if exists
|
||||
let allTabsItem = this._addItemToAllTabsMenu(id, tab, selected);
|
||||
|
||||
let onIFrameLoaded = (event) => {
|
||||
let doc = event.target;
|
||||
let win = doc.defaultView;
|
||||
tab.setAttribute("label", doc.title);
|
||||
|
||||
if (allTabsItem) {
|
||||
allTabsItem.setAttribute("label", doc.title);
|
||||
}
|
||||
|
||||
iframe.removeEventListener("load", onIFrameLoaded, true);
|
||||
if ("setPanel" in win) {
|
||||
win.setPanel(this._toolPanel, iframe);
|
||||
@ -90,7 +247,7 @@ ToolSidebar.prototype = {
|
||||
iframe.addEventListener("load", onIFrameLoaded, true);
|
||||
|
||||
let tabpanel = this._panelDoc.createElementNS(XULNS, "tabpanel");
|
||||
tabpanel.setAttribute("id", "sidebar-panel-" + id);
|
||||
tabpanel.setAttribute("id", this.TABPANEL_ID_PREFIX + id);
|
||||
tabpanel.appendChild(iframe);
|
||||
this._tabbox.tabpanels.appendChild(tabpanel);
|
||||
|
||||
@ -99,7 +256,7 @@ ToolSidebar.prototype = {
|
||||
tabpanel.appendChild(this._tooltip);
|
||||
this._tooltip.page = true;
|
||||
|
||||
tab.linkedPanel = "sidebar-panel-" + id;
|
||||
tab.linkedPanel = this.TABPANEL_ID_PREFIX + id;
|
||||
|
||||
// We store the index of this tab.
|
||||
this._tabs.set(id, tab);
|
||||
@ -116,37 +273,78 @@ ToolSidebar.prototype = {
|
||||
this.emit("new-tab-registered", id);
|
||||
},
|
||||
|
||||
untitledTabsIndex: 0,
|
||||
|
||||
/**
|
||||
* Search for existing tabs in the markup that aren't know yet and add them.
|
||||
*/
|
||||
addExistingTabs: function() {
|
||||
let knownTabs = [...this._tabs.values()];
|
||||
|
||||
for (let tab of this._tabbox.tabs.querySelectorAll("tab")) {
|
||||
if (knownTabs.indexOf(tab) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find an ID for this unknown tab
|
||||
let id = tab.getAttribute("id") || "untitled-tab-" + (this.untitledTabsIndex++);
|
||||
|
||||
// Register the tab
|
||||
this._tabs.set(id, tab);
|
||||
this.emit("new-tab-registered", id);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove an existing tab.
|
||||
* @param {String} tabId The ID of the tab that was used to register it, or
|
||||
* the tab id attribute value if the tab existed before the sidebar got created.
|
||||
* @param {String} tabPanelId Optional. If provided, this ID will be used
|
||||
* instead of the tabId to retrieve and remove the corresponding <tabpanel>
|
||||
*/
|
||||
removeTab: Task.async(function*(id) {
|
||||
let tab = this._tabbox.tabs.querySelector("tab#sidebar-tab-" + id);
|
||||
removeTab: Task.async(function*(tabId, tabPanelId) {
|
||||
// Remove the tab if it can be found
|
||||
let tab = this.getTab(tabId);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
|
||||
let win = this.getWindowForTab(id);
|
||||
if ("destroy" in win) {
|
||||
let win = this.getWindowForTab(tabId);
|
||||
if (win && ("destroy" in win)) {
|
||||
yield win.destroy();
|
||||
}
|
||||
|
||||
tab.remove();
|
||||
|
||||
let panel = this.getTab(id);
|
||||
// Also remove the tabpanel
|
||||
let panel = this.getTabPanel(tabPanelId || tabId);
|
||||
if (panel) {
|
||||
panel.remove();
|
||||
}
|
||||
|
||||
this._tabs.delete(id);
|
||||
|
||||
this.emit("tab-unregistered", id);
|
||||
this._tabs.delete(tabId);
|
||||
this.emit("tab-unregistered", tabId);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Show or hide a specific tab
|
||||
*/
|
||||
toggleTab: function(id, isVisible) {
|
||||
let tab = this.getTab(id);
|
||||
if (!tab) {
|
||||
return;
|
||||
}
|
||||
tab.hidden = !isVisible;
|
||||
if (this._allTabsBtn) {
|
||||
this._allTabsBtn.querySelector("#sidebar-alltabs-item-" + id).hidden = !isVisible;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Select a specific tab.
|
||||
*/
|
||||
select: function ToolSidebar_select(id) {
|
||||
let tab = this._tabs.get(id);
|
||||
select: function(id) {
|
||||
let tab = this.getTab(id);
|
||||
if (tab) {
|
||||
this._tabbox.selectedTab = tab;
|
||||
}
|
||||
@ -155,7 +353,7 @@ ToolSidebar.prototype = {
|
||||
/**
|
||||
* Return the id of the selected tab.
|
||||
*/
|
||||
getCurrentTabID: function ToolSidebar_getCurrentTabID() {
|
||||
getCurrentTabID: function() {
|
||||
let currentID = null;
|
||||
for (let [id, tab] of this._tabs) {
|
||||
if (this._tabbox.tabs.selectedItem == tab) {
|
||||
@ -167,42 +365,75 @@ ToolSidebar.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the requested tab based on the id.
|
||||
*
|
||||
* @param String id
|
||||
* unique id of the requested tab.
|
||||
* Returns the requested tab panel based on the id.
|
||||
* @param {String} id
|
||||
* @return {DOMNode}
|
||||
*/
|
||||
getTab: function ToolSidebar_getTab(id) {
|
||||
return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
|
||||
getTabPanel: function(id) {
|
||||
// Search with and without the ID prefix as there might have been existing
|
||||
// tabpanels by the time the sidebar got created
|
||||
return this._tabbox.tabpanels.querySelector("#" + this.TABPANEL_ID_PREFIX + id + ", #" + id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Return the tab based on the provided id, if one was registered with this id.
|
||||
* @param {String} id
|
||||
* @return {DOMNode}
|
||||
*/
|
||||
getTab: function(id) {
|
||||
return this._tabs.get(id);
|
||||
},
|
||||
|
||||
/**
|
||||
* Event handler.
|
||||
*/
|
||||
handleEvent: function ToolSidebar_eventHandler(event) {
|
||||
if (event.type == "select") {
|
||||
if (this._currentTool == this.getCurrentTabID()) {
|
||||
// Tool hasn't changed.
|
||||
return;
|
||||
}
|
||||
handleEvent: function(event) {
|
||||
if (event.type !== "select" || this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
let previousTool = this._currentTool;
|
||||
this._currentTool = this.getCurrentTabID();
|
||||
if (previousTool) {
|
||||
if (this._currentTool == this.getCurrentTabID()) {
|
||||
// Tool hasn't changed.
|
||||
return;
|
||||
}
|
||||
|
||||
let previousTool = this._currentTool;
|
||||
this._currentTool = this.getCurrentTabID();
|
||||
if (previousTool) {
|
||||
if (this._telemetry) {
|
||||
this._telemetry.toolClosed(previousTool);
|
||||
this.emit(previousTool + "-unselected");
|
||||
}
|
||||
this.emit(previousTool + "-unselected");
|
||||
}
|
||||
|
||||
if (this._telemetry) {
|
||||
this._telemetry.toolOpened(this._currentTool);
|
||||
this.emit(this._currentTool + "-selected");
|
||||
this.emit("select", this._currentTool);
|
||||
}
|
||||
|
||||
this.emit(this._currentTool + "-selected");
|
||||
this.emit("select", this._currentTool);
|
||||
|
||||
// Handlers for "select"/"...-selected"/"...-unselected" events might have
|
||||
// destroyed the sidebar in the meantime.
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle menuitem selection if the allTabsMenu is there by unchecking all
|
||||
// items except the selected one.
|
||||
let tab = this._tabbox.selectedTab;
|
||||
if (tab.allTabsMenuItem) {
|
||||
for (let otherItem of this._allTabsBtn.querySelectorAll("menuitem")) {
|
||||
otherItem.removeAttribute("checked");
|
||||
}
|
||||
tab.allTabsMenuItem.setAttribute("checked", true);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Toggle sidebar's visibility state.
|
||||
*/
|
||||
toggle: function ToolSidebar_toggle() {
|
||||
toggle: function() {
|
||||
if (this._tabbox.hasAttribute("hidden")) {
|
||||
this.show();
|
||||
} else {
|
||||
@ -213,7 +444,7 @@ ToolSidebar.prototype = {
|
||||
/**
|
||||
* Show the sidebar.
|
||||
*/
|
||||
show: function ToolSidebar_show() {
|
||||
show: function() {
|
||||
if (this._width) {
|
||||
this._tabbox.width = this._width;
|
||||
}
|
||||
@ -225,7 +456,7 @@ ToolSidebar.prototype = {
|
||||
/**
|
||||
* Show the sidebar.
|
||||
*/
|
||||
hide: function ToolSidebar_hide() {
|
||||
hide: function() {
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
|
||||
this._tabbox.setAttribute("hidden", "true");
|
||||
|
||||
@ -235,12 +466,16 @@ ToolSidebar.prototype = {
|
||||
/**
|
||||
* Return the window containing the tab content.
|
||||
*/
|
||||
getWindowForTab: function ToolSidebar_getWindowForTab(id) {
|
||||
getWindowForTab: function(id) {
|
||||
if (!this._tabs.has(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let panel = this._panelDoc.getElementById(this._tabs.get(id).linkedPanel);
|
||||
// Get the tabpanel and make sure it contains an iframe
|
||||
let panel = this.getTabPanel(id);
|
||||
if (!panel || !panel.firstChild || !panel.firstChild.contentWindow) {
|
||||
return;
|
||||
}
|
||||
return panel.firstChild.contentWindow;
|
||||
},
|
||||
|
||||
@ -249,12 +484,16 @@ ToolSidebar.prototype = {
|
||||
*/
|
||||
destroy: Task.async(function*() {
|
||||
if (this._destroyed) {
|
||||
return promise.resolve(null);
|
||||
return;
|
||||
}
|
||||
this._destroyed = true;
|
||||
|
||||
Services.prefs.setIntPref("devtools.toolsidebar-width." + this._uid, this._tabbox.width);
|
||||
|
||||
if (this._allTabsBtn) {
|
||||
this.removeAllTabsMenu();
|
||||
}
|
||||
|
||||
this._tabbox.tabpanels.removeEventListener("select", this, true);
|
||||
|
||||
// Note that we check for the existence of this._tabbox.tabpanels at each
|
||||
@ -263,7 +502,7 @@ ToolSidebar.prototype = {
|
||||
while (this._tabbox.tabpanels && this._tabbox.tabpanels.hasChildNodes()) {
|
||||
let panel = this._tabbox.tabpanels.firstChild;
|
||||
let win = panel.firstChild.contentWindow;
|
||||
if ("destroy" in win) {
|
||||
if (win && ("destroy" in win)) {
|
||||
yield win.destroy();
|
||||
}
|
||||
panel.remove();
|
||||
@ -273,7 +512,7 @@ ToolSidebar.prototype = {
|
||||
this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
|
||||
}
|
||||
|
||||
if (this._currentTool) {
|
||||
if (this._currentTool && this._telemetry) {
|
||||
this._telemetry.toolClosed(this._currentTool);
|
||||
}
|
||||
|
||||
@ -283,7 +522,21 @@ ToolSidebar.prototype = {
|
||||
this._tabbox = null;
|
||||
this._panelDoc = null;
|
||||
this._toolPanel = null;
|
||||
|
||||
return promise.resolve(null);
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "l10n", function() {
|
||||
let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
|
||||
let l10n = function(aName, ...aArgs) {
|
||||
try {
|
||||
if (aArgs.length == 0) {
|
||||
return bundle.GetStringFromName(aName);
|
||||
} else {
|
||||
return bundle.formatStringFromName(aName, aArgs, aArgs.length);
|
||||
}
|
||||
} catch (ex) {
|
||||
Services.console.logStringMessage("Error reading '" + aName + "'");
|
||||
}
|
||||
};
|
||||
return l10n;
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ support-files =
|
||||
browser_toolbox_options_disable_js.html
|
||||
browser_toolbox_options_disable_js_iframe.html
|
||||
browser_toolbox_options_disable_cache.sjs
|
||||
browser_toolbox_sidebar_tool.xul
|
||||
head.js
|
||||
helper_disable_cache.js
|
||||
doc_theme.css
|
||||
@ -39,6 +40,8 @@ skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e1
|
||||
skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
|
||||
[browser_toolbox_sidebar.js]
|
||||
[browser_toolbox_sidebar_events.js]
|
||||
[browser_toolbox_sidebar_existing_tabs.js]
|
||||
[browser_toolbox_sidebar_overflow_menu.js]
|
||||
[browser_toolbox_tabsswitch_shortcuts.js]
|
||||
[browser_toolbox_tool_ready.js]
|
||||
[browser_toolbox_tool_remote_reopen.js]
|
||||
|
@ -3,8 +3,7 @@
|
||||
|
||||
// Tests that disabling JavaScript for a tab works as it should.
|
||||
|
||||
const TEST_URI = "http://example.com/browser/browser/devtools/framework/" +
|
||||
"test/browser_toolbox_options_disable_js.html";
|
||||
const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_js.html";
|
||||
|
||||
let doc;
|
||||
let toolbox;
|
||||
|
@ -111,7 +111,7 @@ function test() {
|
||||
|
||||
is(label, 4, "Found the right amount of tabs.");
|
||||
is(panel.sidebar._tabbox.selectedPanel, panels[0], "First tab is selected");
|
||||
ok(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
|
||||
is(panel.sidebar.getCurrentTabID(), "tab1", "getCurrentTabID() is correct");
|
||||
|
||||
panel.sidebar.once("tab1-unselected", function() {
|
||||
ok(true, "received 'unselected' event");
|
||||
@ -154,6 +154,7 @@ function test() {
|
||||
panel.sidebar = new ToolSidebar(tabbox, panel, "testbug865688", true);
|
||||
panel.sidebar.show();
|
||||
is(panel.panelDoc.getElementById("sidebar").width, 420, "Width restored")
|
||||
|
||||
finishUp(panel);
|
||||
});
|
||||
}
|
||||
|
@ -0,0 +1,76 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that the sidebar widget auto-registers existing tabs.
|
||||
|
||||
const Cu = Components.utils;
|
||||
const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
|
||||
|
||||
const testToolURL = "data:text/xml;charset=utf8,<?xml version='1.0'?>" +
|
||||
"<?xml-stylesheet href='chrome://browser/skin/devtools/common.css' type='text/css'?>" +
|
||||
"<window xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'>" +
|
||||
"<hbox flex='1'><description flex='1'>test tool</description>" +
|
||||
"<splitter class='devtools-side-splitter'/>" +
|
||||
"<tabbox flex='1' id='sidebar' class='devtools-sidebar-tabs'>" +
|
||||
"<tabs><tab id='tab1' label='tab 1'></tab><tab id='tab2' label='tab 2'></tab></tabs>" +
|
||||
"<tabpanels flex='1'><tabpanel id='tabpanel1'>tab 1</tabpanel><tabpanel id='tabpanel2'>tab 2</tabpanel></tabpanels>" +
|
||||
"</tabbox></hbox></window>";
|
||||
|
||||
const testToolDefinition = {
|
||||
id: "testTool",
|
||||
url: testToolURL,
|
||||
label: "Test Tool",
|
||||
isTargetSupported: () => true,
|
||||
build: (iframeWindow, toolbox) => {
|
||||
return promise.resolve({
|
||||
target: toolbox.target,
|
||||
toolbox: toolbox,
|
||||
isReady: true,
|
||||
destroy: () => {},
|
||||
panelDoc: iframeWindow.document,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("about:blank");
|
||||
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
||||
gDevTools.registerTool(testToolDefinition);
|
||||
let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
|
||||
|
||||
let toolPanel = toolbox.getPanel(testToolDefinition.id);
|
||||
let tabbox = toolPanel.panelDoc.getElementById("sidebar");
|
||||
|
||||
info("Creating the sidebar widget");
|
||||
let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569");
|
||||
|
||||
info("Checking that existing tabs have been registered");
|
||||
ok(sidebar.getTab("tab1"), "Existing tab 1 was found");
|
||||
ok(sidebar.getTab("tab2"), "Existing tab 2 was found");
|
||||
ok(sidebar.getTabPanel("tabpanel1"), "Existing tabpanel 1 was found");
|
||||
ok(sidebar.getTabPanel("tabpanel2"), "Existing tabpanel 2 was found");
|
||||
|
||||
info("Checking that the sidebar API works with existing tabs");
|
||||
|
||||
sidebar.select("tab2");
|
||||
is(tabbox.selectedTab, tabbox.querySelector("#tab2"),
|
||||
"Existing tabs can be selected");
|
||||
|
||||
sidebar.select("tab1");
|
||||
is(tabbox.selectedTab, tabbox.querySelector("#tab1"),
|
||||
"Existing tabs can be selected");
|
||||
|
||||
is(sidebar.getCurrentTabID(), "tab1", "getCurrentTabID returns the expected id");
|
||||
|
||||
info("Removing a tab");
|
||||
sidebar.removeTab("tab2", "tabpanel2");
|
||||
ok(!sidebar.getTab("tab2"), "Tab 2 was removed correctly");
|
||||
ok(!sidebar.getTabPanel("tabpanel2"), "Tabpanel 2 was removed correctly");
|
||||
|
||||
sidebar.destroy();
|
||||
gDevTools.unregisterTool(testToolDefinition.id);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -0,0 +1,76 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
"use strict";
|
||||
|
||||
// Test that the sidebar widget correctly displays the "all tabs..." button
|
||||
// when the tabs overflow.
|
||||
|
||||
const {ToolSidebar} = devtools.require("devtools/framework/sidebar");
|
||||
|
||||
const testToolDefinition = {
|
||||
id: "testTool",
|
||||
url: CHROME_URL_ROOT + "browser_toolbox_sidebar_tool.xul",
|
||||
label: "Test Tool",
|
||||
isTargetSupported: () => true,
|
||||
build: (iframeWindow, toolbox) => {
|
||||
return {
|
||||
target: toolbox.target,
|
||||
toolbox: toolbox,
|
||||
isReady: true,
|
||||
destroy: () => {},
|
||||
panelDoc: iframeWindow.document,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield addTab("about:blank");
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
||||
gDevTools.registerTool(testToolDefinition);
|
||||
let toolbox = yield gDevTools.showToolbox(target, testToolDefinition.id);
|
||||
|
||||
let toolPanel = toolbox.getPanel(testToolDefinition.id);
|
||||
let tabbox = toolPanel.panelDoc.getElementById("sidebar");
|
||||
|
||||
info("Creating the sidebar widget");
|
||||
let sidebar = new ToolSidebar(tabbox, toolPanel, "bug1101569", {
|
||||
showAllTabsMenu: true
|
||||
});
|
||||
|
||||
let allTabsMenu = toolPanel.panelDoc.querySelector(".devtools-sidebar-alltabs");
|
||||
ok(allTabsMenu, "The all-tabs menu is available");
|
||||
is(allTabsMenu.getAttribute("hidden"), "true", "The menu is hidden for now");
|
||||
|
||||
info("Adding 10 tabs to the sidebar widget");
|
||||
for (let nb = 0; nb < 10; nb ++) {
|
||||
let url = `data:text/html;charset=utf8,<title>tab ${nb}</title><p>Test tab ${nb}</p>`;
|
||||
sidebar.addTab("tab" + nb, url, nb === 0);
|
||||
}
|
||||
|
||||
info("Fake an overflow event so that the all-tabs menu is visible");
|
||||
sidebar._onTabBoxOverflow();
|
||||
ok(!allTabsMenu.hasAttribute("hidden"), "The all-tabs menu is now shown");
|
||||
|
||||
info("Select each tab, one by one");
|
||||
for (let nb = 0; nb < 10; nb ++) {
|
||||
let id = "tab" + nb;
|
||||
|
||||
info("Found tab item nb " + nb);
|
||||
let item = allTabsMenu.querySelector("#sidebar-alltabs-item-" + id);
|
||||
|
||||
info("Click on the tab");
|
||||
EventUtils.sendMouseEvent({type: "click"}, item, toolPanel.panelDoc.defaultView);
|
||||
|
||||
is(tabbox.selectedTab.id, "sidebar-tab-" + id,
|
||||
"The selected tab is now nb " + nb);
|
||||
}
|
||||
|
||||
info("Fake an underflow event so that the all-tabs menu gets hidden");
|
||||
sidebar._onTabBoxUnderflow();
|
||||
is(allTabsMenu.getAttribute("hidden"), "true", "The all-tabs menu is hidden");
|
||||
|
||||
yield sidebar.destroy();
|
||||
gDevTools.unregisterTool(testToolDefinition.id);
|
||||
gBrowser.removeCurrentTab();
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script type="application/javascript;version=1.8" src="chrome://browser/content/devtools/theme-switching.js"/>
|
||||
<box flex="1" class="devtools-responsive-container theme-body">
|
||||
<vbox flex="1" class="devtools-main-content" id="content">test</vbox>
|
||||
<splitter class="devtools-side-splitter"/>
|
||||
<tabbox flex="1" id="sidebar" class="devtools-sidebar-tabs">
|
||||
<tabs/>
|
||||
<tabpanels flex="1"/>
|
||||
</tabbox>
|
||||
</box>
|
||||
</window>
|
@ -8,6 +8,9 @@ const { console } = Cu.import("resource://gre/modules/devtools/Console.jsm", {})
|
||||
const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
|
||||
const { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
|
||||
const URL_ROOT = "http://example.com/browser/browser/devtools/framework/test/";
|
||||
const CHROME_URL_ROOT = "chrome://mochitests/content/browser/browser/devtools/framework/test/";
|
||||
|
||||
let TargetFactory = devtools.TargetFactory;
|
||||
|
||||
// All test are asynchronous
|
||||
|
@ -4,8 +4,7 @@
|
||||
"use strict";
|
||||
|
||||
// Common code shared by browser_toolbox_options_disable_cache-*.js
|
||||
const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/framework/" +
|
||||
"test/browser_toolbox_options_disable_cache.sjs";
|
||||
const TEST_URI = URL_ROOT + "browser_toolbox_options_disable_cache.sjs";
|
||||
let tabs = [
|
||||
{
|
||||
title: "Tab 0",
|
||||
|
@ -347,8 +347,6 @@ InspectorPanel.prototype = {
|
||||
"animationinspector" == defaultTab);
|
||||
}
|
||||
|
||||
let ruleViewTab = this.sidebar.getTab("ruleview");
|
||||
|
||||
this.sidebar.show();
|
||||
},
|
||||
|
||||
|
@ -2129,7 +2129,7 @@ function ElementEditor(aContainer, aNode) {
|
||||
}
|
||||
|
||||
// Make the new attribute space editable.
|
||||
editableField({
|
||||
this.newAttr.editMode = editableField({
|
||||
element: this.newAttr,
|
||||
trigger: "dblclick",
|
||||
stopOnReturn: true,
|
||||
@ -2235,7 +2235,7 @@ ElementEditor.prototype = {
|
||||
}
|
||||
|
||||
// Make the attribute editable.
|
||||
editableField({
|
||||
attr.editMode = editableField({
|
||||
element: inner,
|
||||
trigger: "dblclick",
|
||||
stopOnReturn: true,
|
||||
@ -2257,7 +2257,7 @@ ElementEditor.prototype = {
|
||||
aEditor.input.select();
|
||||
}
|
||||
},
|
||||
done: (aVal, aCommit) => {
|
||||
done: (aVal, aCommit, direction) => {
|
||||
if (!aCommit || aVal === initial) {
|
||||
return;
|
||||
}
|
||||
@ -2269,6 +2269,7 @@ ElementEditor.prototype = {
|
||||
// parsed out of the input element. Restore original attribute if
|
||||
// parsing fails.
|
||||
try {
|
||||
this.refocusOnEdit(aAttr.name, attr, direction);
|
||||
this._saveAttribute(aAttr.name, undoMods);
|
||||
doMods.removeAttribute(aAttr.name);
|
||||
this._applyAttributes(aVal, attr, doMods, undoMods);
|
||||
@ -2347,6 +2348,97 @@ ElementEditor.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen to mutations, and when the attribute list is regenerated
|
||||
* try to focus on the attribute after the one that's being edited now.
|
||||
* If the attribute order changes, go to the beginning of the attribute list.
|
||||
*/
|
||||
refocusOnEdit: function(attrName, attrNode, direction) {
|
||||
// Only allow one refocus on attribute change at a time, so when there's
|
||||
// more than 1 request in parallel, the last one wins.
|
||||
if (this._editedAttributeObserver) {
|
||||
this.markup._inspector.off("markupmutation", this._editedAttributeObserver);
|
||||
this._editedAttributeObserver = null;
|
||||
}
|
||||
|
||||
let container = this.markup.getContainer(this.node);
|
||||
|
||||
let activeAttrs = [...this.attrList.childNodes].filter(el => el.style.display != "none");
|
||||
let attributeIndex = activeAttrs.indexOf(attrNode);
|
||||
|
||||
let onMutations = this._editedAttributeObserver = (e, mutations) => {
|
||||
let isDeletedAttribute = false;
|
||||
let isNewAttribute = false;
|
||||
for (let mutation of mutations) {
|
||||
let inContainer = this.markup.getContainer(mutation.target) === container;
|
||||
if (!inContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let isOriginalAttribute = mutation.attributeName === attrName;
|
||||
|
||||
isDeletedAttribute = isDeletedAttribute || isOriginalAttribute && mutation.newValue === null;
|
||||
isNewAttribute = isNewAttribute || mutation.attributeName !== attrName;
|
||||
}
|
||||
let isModifiedOrder = isDeletedAttribute && isNewAttribute;
|
||||
this._editedAttributeObserver = null;
|
||||
|
||||
// "Deleted" attributes are merely hidden, so filter them out.
|
||||
let visibleAttrs = [...this.attrList.childNodes].filter(el => el.style.display != "none");
|
||||
let activeEditor;
|
||||
if (visibleAttrs.length > 0) {
|
||||
if (!direction) {
|
||||
// No direction was given; stay on current attribute.
|
||||
activeEditor = visibleAttrs[attributeIndex];
|
||||
} else if (isModifiedOrder) {
|
||||
// The attribute was renamed, reordering the existing attributes.
|
||||
// So let's go to the beginning of the attribute list for consistency.
|
||||
activeEditor = visibleAttrs[0];
|
||||
} else {
|
||||
let newAttributeIndex;
|
||||
if (isDeletedAttribute) {
|
||||
newAttributeIndex = attributeIndex;
|
||||
} else {
|
||||
if (direction == Ci.nsIFocusManager.MOVEFOCUS_FORWARD) {
|
||||
newAttributeIndex = attributeIndex + 1;
|
||||
} else if (direction == Ci.nsIFocusManager.MOVEFOCUS_BACKWARD) {
|
||||
newAttributeIndex = attributeIndex - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The number of attributes changed (deleted), or we moved through the array
|
||||
// so check we're still within bounds.
|
||||
if (newAttributeIndex >= 0 && newAttributeIndex <= visibleAttrs.length - 1) {
|
||||
activeEditor = visibleAttrs[newAttributeIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Either we have no attributes left,
|
||||
// or we just edited the last attribute and want to move on.
|
||||
if (!activeEditor) {
|
||||
activeEditor = this.newAttr;
|
||||
}
|
||||
|
||||
// Refocus was triggered by tab or shift-tab.
|
||||
// Continue in edit mode.
|
||||
if (direction) {
|
||||
activeEditor.editMode();
|
||||
} else {
|
||||
// Refocus was triggered by enter.
|
||||
// Exit edit mode (but restore focus).
|
||||
let editable = activeEditor === this.newAttr ? activeEditor : activeEditor.querySelector(".editable");
|
||||
editable.focus();
|
||||
}
|
||||
|
||||
this.markup.emit("refocusedonedit");
|
||||
};
|
||||
|
||||
// Start listening for mutations until we find an attributes change
|
||||
// that modifies this attribute.
|
||||
this.markup._inspector.once("markupmutation", onMutations);
|
||||
},
|
||||
|
||||
/**
|
||||
* Called when the tag name editor has is done editing.
|
||||
*/
|
||||
|
@ -63,6 +63,7 @@ skip-if = e10s # Bug 1040751 - CodeMirror editor.destroy() isn't e10s compatible
|
||||
[browser_markupview_html_edit_02.js]
|
||||
[browser_markupview_html_edit_03.js]
|
||||
[browser_markupview_image_tooltip.js]
|
||||
[browser_markupview_keybindings_01.js]
|
||||
[browser_markupview_mutation_01.js]
|
||||
[browser_markupview_mutation_02.js]
|
||||
[browser_markupview_navigation.js]
|
||||
@ -83,6 +84,7 @@ skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
|
||||
[browser_markupview_tag_edit_09.js]
|
||||
[browser_markupview_tag_edit_10.js]
|
||||
[browser_markupview_tag_edit_11.js]
|
||||
[browser_markupview_tag_edit_12.js]
|
||||
[browser_markupview_textcontent_edit_01.js]
|
||||
[browser_markupview_toggle_01.js]
|
||||
[browser_markupview_toggle_02.js]
|
||||
|
@ -0,0 +1,47 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests tabbing through attributes on a node
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf8,<div a b c d e id='test'></div>";
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
info("Focusing the tag editor of the test element");
|
||||
let {editor} = yield getContainerForSelector("div", inspector);
|
||||
editor.tag.focus();
|
||||
|
||||
info("Pressing tab and expecting to focus the ID attribute, always first");
|
||||
EventUtils.sendKey("tab", inspector.panelWin);
|
||||
checkFocusedAttribute("id");
|
||||
|
||||
info("Hit enter to turn the attribute to edit mode");
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
checkFocusedAttribute("id", true);
|
||||
|
||||
// Check the order of the other attributes in the DOM to the check they appear
|
||||
// correctly in the markup-view
|
||||
let attributes = [...getNode("div").attributes].filter(attr => attr.name !== "id");
|
||||
|
||||
info("Tabbing forward through attributes in edit mode");
|
||||
for (let {name} of attributes) {
|
||||
collapseSelectionAndTab(inspector);
|
||||
checkFocusedAttribute(name, true);
|
||||
}
|
||||
|
||||
info("Tabbing backward through attributes in edit mode");
|
||||
|
||||
// Just reverse the attributes other than id and remove the first one since
|
||||
// it's already focused now.
|
||||
let reverseAttributes = attributes.reverse();
|
||||
reverseAttributes.shift();
|
||||
|
||||
for (let {name} of reverseAttributes) {
|
||||
collapseSelectionAndShiftTab(inspector);
|
||||
checkFocusedAttribute(name, true);
|
||||
}
|
||||
});
|
@ -0,0 +1,115 @@
|
||||
/* vim: set ts=2 et sw=2 tw=80: */
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests that focus position is correct when tabbing through and editing
|
||||
// attributes.
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf8,<div id='attr' c='3' b='2' a='1'></div><div id='delattr' last='1' tobeinvalid='2'></div>";
|
||||
|
||||
add_task(function*() {
|
||||
let {inspector} = yield addTab(TEST_URL).then(openInspector);
|
||||
|
||||
yield testAttributeEditing(inspector);
|
||||
yield testAttributeDeletion(inspector);
|
||||
});
|
||||
|
||||
function* testAttributeEditing(inspector) {
|
||||
info("Testing focus position after attribute editing");
|
||||
|
||||
// Modifying attributes reorders them in the internal representation to move
|
||||
// the modified attribute to the end. breadcrumbs.js will update attributes
|
||||
// to match original order if you selectNode before modifying attributes.
|
||||
// So, hacky workaround for consistency with manual testing.
|
||||
// Should be removed after Bug 1093593.
|
||||
yield selectNode("#attr", inspector);
|
||||
|
||||
info("Setting the first non-id attribute in edit mode");
|
||||
yield activateFirstAttribute("#attr", inspector); // focuses id
|
||||
collapseSelectionAndTab(inspector); // focuses the first attr after id
|
||||
|
||||
// Detect the attributes order from the DOM, instead of assuming an order in
|
||||
// the test, because the NamedNodeMap returned by element.attributes doesn't
|
||||
// guaranty any specific order.
|
||||
// Filter out the id attribute as the markup-view places it first anyway.
|
||||
let attrs = getNodeAttributesOtherThanId("#attr");
|
||||
|
||||
info("Editing this attribute, keeping the same name, and tabbing to the next");
|
||||
yield editAttributeAndTab(attrs[0].name + '="99"', inspector);
|
||||
checkFocusedAttribute(attrs[1].name, true);
|
||||
|
||||
info("Editing the new focused attribute, keeping the name, and tabbing to the previous");
|
||||
yield editAttributeAndTab(attrs[1].name + '="99"', inspector, true);
|
||||
checkFocusedAttribute(attrs[0].name, true);
|
||||
|
||||
info("Editing attribute name, changes attribute order");
|
||||
yield editAttributeAndTab("d='4'", inspector);
|
||||
checkFocusedAttribute("id", true);
|
||||
|
||||
// Escape of the currently focused field for the next test
|
||||
EventUtils.sendKey("escape", inspector.panelWin);
|
||||
}
|
||||
|
||||
function* testAttributeDeletion(inspector) {
|
||||
info("Testing focus position after attribute deletion");
|
||||
|
||||
// Modifying attributes reorders them in the internal representation to move
|
||||
// the modified attribute to the end. breadcrumbs.js will update attributes
|
||||
// to match original order if you selectNode before modifying attributes.
|
||||
// So, hacky workaround for consistency with manual testing.
|
||||
// Should be removed after Bug 1093593.
|
||||
yield selectNode("#delattr", inspector);
|
||||
|
||||
info("Setting the first non-id attribute in edit mode");
|
||||
yield activateFirstAttribute("#delattr", inspector); // focuses id
|
||||
collapseSelectionAndTab(inspector); // focuses the first attr after id
|
||||
|
||||
// Detect the attributes order from the DOM, instead of assuming an order in
|
||||
// the test, because the NamedNodeMap returned by element.attributes doesn't
|
||||
// guaranty any specific order.
|
||||
// Filter out the id attribute as the markup-view places it first anyway.
|
||||
let attrs = getNodeAttributesOtherThanId("#delattr");
|
||||
|
||||
info("Entering an invalid attribute to delete the attribute");
|
||||
yield editAttributeAndTab('"', inspector);
|
||||
checkFocusedAttribute(attrs[1].name, true);
|
||||
|
||||
info("Deleting the last attribute");
|
||||
yield editAttributeAndTab(" ", inspector);
|
||||
|
||||
// Check we're on the newattr element
|
||||
let focusedAttr = Services.focus.focusedElement;
|
||||
ok(focusedAttr.classList.contains("styleinspector-propertyeditor"), "in newattr");
|
||||
is(focusedAttr.tagName, "input", "newattr is active");
|
||||
}
|
||||
|
||||
function* editAttributeAndTab(newValue, inspector, goPrevious) {
|
||||
var onEditMutation = inspector.markup.once("refocusedonedit");
|
||||
inspector.markup.doc.activeElement.value = newValue;
|
||||
if (goPrevious) {
|
||||
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
|
||||
inspector.panelWin);
|
||||
} else {
|
||||
EventUtils.sendKey("tab", inspector.panelWin);
|
||||
}
|
||||
yield onEditMutation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a markup container, focus and turn in edit mode its first attribute
|
||||
* field.
|
||||
*/
|
||||
function* activateFirstAttribute(container, inspector) {
|
||||
let {editor} = yield getContainerForSelector(container, inspector);
|
||||
editor.tag.focus();
|
||||
|
||||
// Go to "id" attribute and trigger edit mode.
|
||||
EventUtils.sendKey("tab", inspector.panelWin);
|
||||
EventUtils.sendKey("return", inspector.panelWin);
|
||||
}
|
||||
|
||||
function getNodeAttributesOtherThanId(selector) {
|
||||
return [...getNode(selector).attributes].filter(attr => attr.name !== "id");
|
||||
}
|
@ -526,3 +526,38 @@ function promiseNextTick() {
|
||||
executeSoon(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses the current text selection in an input field and tabs to the next
|
||||
* field.
|
||||
*/
|
||||
function collapseSelectionAndTab(inspector) {
|
||||
EventUtils.sendKey("tab", inspector.panelWin); // collapse selection and move caret to end
|
||||
EventUtils.sendKey("tab", inspector.panelWin); // next element
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses the current text selection in an input field and tabs to the
|
||||
* previous field.
|
||||
*/
|
||||
function collapseSelectionAndShiftTab(inspector) {
|
||||
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
|
||||
inspector.panelWin); // collapse selection and move caret to end
|
||||
EventUtils.synthesizeKey("VK_TAB", { shiftKey: true },
|
||||
inspector.panelWin); // previous element
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the current focused element is an attribute element in the markup
|
||||
* view.
|
||||
* @param {String} attrName The attribute name expected to be found
|
||||
* @param {Boolean} editMode Whether or not the attribute should be in edit mode
|
||||
*/
|
||||
function checkFocusedAttribute(attrName, editMode) {
|
||||
let focusedAttr = Services.focus.focusedElement;
|
||||
is(focusedAttr ? focusedAttr.parentNode.dataset.attr : undefined,
|
||||
attrName, attrName + " attribute editor is currently focused.");
|
||||
is(focusedAttr ? focusedAttr.tagName : undefined,
|
||||
editMode ? "input": "span",
|
||||
editMode ? attrName + " is in edit mode" : attrName + " is not in edit mode");
|
||||
}
|
||||
|
@ -11,7 +11,7 @@
|
||||
*
|
||||
* editableField({
|
||||
* element: spanToEdit,
|
||||
* done: function(value, commit) {
|
||||
* done: function(value, commit, direction) {
|
||||
* if (commit) {
|
||||
* spanToEdit.textContent = value;
|
||||
* }
|
||||
@ -62,9 +62,11 @@ Cu.import("resource://gre/modules/devtools/event-emitter.js");
|
||||
* with the current value of the text input.
|
||||
* {function} done:
|
||||
* Called when input is committed or blurred. Called with
|
||||
* current value and a boolean telling the caller whether to
|
||||
* commit the change. This function is called before the editor
|
||||
* has been torn down.
|
||||
* current value, a boolean telling the caller whether to
|
||||
* commit the change, and the direction of the next element to be
|
||||
* selected. Direction may be one of nsIFocusManager.MOVEFOCUS_FORWARD,
|
||||
* nsIFocusManager.MOVEFOCUS_BACKWARD, or null (no movement).
|
||||
* This function is called before the editor has been torn down.
|
||||
* {function} destroy:
|
||||
* Called when the editor is destroyed and has been torn down.
|
||||
* {string} advanceChars:
|
||||
@ -103,6 +105,7 @@ exports.editableField = editableField;
|
||||
* defaults to "click"
|
||||
* @param {function} aCallback
|
||||
* Called when the editor is activated.
|
||||
* @return {function} function which calls aCallback
|
||||
*/
|
||||
function editableItem(aOptions, aCallback)
|
||||
{
|
||||
@ -148,6 +151,13 @@ function editableItem(aOptions, aCallback)
|
||||
// Mark the element editable field for tab
|
||||
// navigation while editing.
|
||||
element._editable = true;
|
||||
|
||||
// Save the trigger type so we can dispatch this later
|
||||
element._trigger = trigger;
|
||||
|
||||
return function turnOnEditMode() {
|
||||
aCallback(element);
|
||||
}
|
||||
}
|
||||
|
||||
exports.editableItem = this.editableItem;
|
||||
@ -769,7 +779,7 @@ InplaceEditor.prototype = {
|
||||
/**
|
||||
* Call the client's done handler and clear out.
|
||||
*/
|
||||
_apply: function InplaceEditor_apply(aEvent)
|
||||
_apply: function InplaceEditor_apply(aEvent, direction)
|
||||
{
|
||||
if (this._applied) {
|
||||
return;
|
||||
@ -779,7 +789,7 @@ InplaceEditor.prototype = {
|
||||
|
||||
if (this.done) {
|
||||
let val = this.input.value.trim();
|
||||
return this.done(this.cancelled ? this.initial : val, !this.cancelled);
|
||||
return this.done(this.cancelled ? this.initial : val, !this.cancelled, direction);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -945,7 +955,7 @@ InplaceEditor.prototype = {
|
||||
}
|
||||
}
|
||||
|
||||
this._apply();
|
||||
this._apply(aEvent, direction);
|
||||
|
||||
// Close the popup if open
|
||||
if (this.popup && this.popup.isOpen) {
|
||||
@ -958,9 +968,11 @@ InplaceEditor.prototype = {
|
||||
let next = moveFocus(this.doc.defaultView, direction);
|
||||
|
||||
// If the next node to be focused has been tagged as an editable
|
||||
// node, send it a click event to trigger
|
||||
// node, trigger editing using the configured event
|
||||
if (next && next.ownerDocument === this.doc && next._editable) {
|
||||
next.click();
|
||||
let e = this.doc.createEvent('Event');
|
||||
e.initEvent(next._trigger, true, true);
|
||||
next.dispatchEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -441,7 +441,7 @@ StyleSheetEditor.prototype = {
|
||||
* Toggled the disabled state of the underlying stylesheet.
|
||||
*/
|
||||
toggleDisabled: function() {
|
||||
this.styleSheet.toggleDisabled();
|
||||
this.styleSheet.toggleDisabled().then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -483,7 +483,8 @@ StyleSheetEditor.prototype = {
|
||||
|
||||
let transitionsEnabled = Services.prefs.getBoolPref(TRANSITION_PREF);
|
||||
|
||||
this.styleSheet.update(this._state.text, transitionsEnabled);
|
||||
this.styleSheet.update(this._state.text, transitionsEnabled)
|
||||
.then(null, Cu.reportError);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -54,18 +54,21 @@ add_task(function*() {
|
||||
yield TimelineController.toggleRecording();
|
||||
ok(true, "Recording has ended.");
|
||||
|
||||
is(TimelineController.getMarkers().length, 0,
|
||||
"There are no markers available.");
|
||||
// TODO: Re-enable this assertion as part of bug 1120830
|
||||
// is(TimelineController.getMarkers().length, 0,
|
||||
// "There are no markers available.");
|
||||
isnot(TimelineController.getMemory().length, 0,
|
||||
"There are some memory measurements available.");
|
||||
|
||||
is(TimelineView.markersOverview.selectionEnabled, true,
|
||||
"The selection should now be enabled for the markers overview.");
|
||||
is(TimelineView.markersOverview.hasSelection(), false,
|
||||
"The markers overview should not have a selection after recording.");
|
||||
// TODO: Re-enable this assertion as part of bug 1120830
|
||||
// is(TimelineView.markersOverview.hasSelection(), false,
|
||||
// "The markers overview should not have a selection after recording.");
|
||||
|
||||
is(TimelineView.memoryOverview.selectionEnabled, true,
|
||||
"The selection should now be enabled for the memory overview.");
|
||||
is(TimelineView.memoryOverview.hasSelection(), false,
|
||||
"The memory overview should not have a selection after recording.");
|
||||
// TODO: Re-enable this assertion as part of bug 1120830
|
||||
// is(TimelineView.memoryOverview.hasSelection(), false,
|
||||
// "The memory overview should not have a selection after recording.");
|
||||
});
|
||||
|
@ -3555,8 +3555,8 @@ JSTerm.prototype = {
|
||||
deferred.resolve(window);
|
||||
};
|
||||
|
||||
let tab = this.sidebar.getTab("variablesview");
|
||||
if (tab) {
|
||||
let tabPanel = this.sidebar.getTabPanel("variablesview");
|
||||
if (tabPanel) {
|
||||
if (this.sidebar.getCurrentTabID() == "variablesview") {
|
||||
onTabReady();
|
||||
}
|
||||
|
@ -589,9 +589,6 @@ Section "-Application" APP_IDX
|
||||
${EndUnless}
|
||||
${EndIf}
|
||||
|
||||
; Add the Firewall entries during install
|
||||
Call AddFirewallEntries
|
||||
|
||||
!ifdef MOZ_MAINTENANCE_SERVICE
|
||||
${If} $TmpVal == "HKLM"
|
||||
; Add the registry keys for allowed certificates.
|
||||
@ -627,6 +624,9 @@ Section "-InstallEndCleanup"
|
||||
${GetShortcutsLogPath} $0
|
||||
WriteIniStr "$0" "TASKBAR" "Migrated" "true"
|
||||
|
||||
; Add the Firewall entries during install
|
||||
Call AddFirewallEntries
|
||||
|
||||
; Refresh desktop icons
|
||||
System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_DWORDFLUSH}, i 0, i 0)"
|
||||
|
||||
|
@ -938,7 +938,7 @@ FunctionEnd
|
||||
!macroend
|
||||
!define MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
|
||||
!define un.MountRegistryIntoHKU "!insertmacro MountRegistryIntoHKU"
|
||||
;
|
||||
|
||||
; Unmounts all user ntuser.dat files into the registry as a subkey of HKU
|
||||
!macro UnmountRegistryIntoHKU
|
||||
; $0 is used as an index for HKEY_USERS enumeration
|
||||
@ -1556,6 +1556,72 @@ FunctionEnd
|
||||
!macroend
|
||||
!define PushFilesToCheck "!insertmacro PushFilesToCheck"
|
||||
|
||||
|
||||
; Pushes the string "true" to the top of the stack if the Firewall service is
|
||||
; running and pushes the string "false" to the top of the stack if it isn't.
|
||||
!define SC_MANAGER_ALL_ACCESS 0x3F
|
||||
!define SERVICE_QUERY_CONFIG 0x0001
|
||||
!define SERVICE_QUERY_STATUS 0x0004
|
||||
!define SERVICE_RUNNING 0x4
|
||||
|
||||
!macro IsFirewallSvcRunning
|
||||
Push $R9
|
||||
Push $R8
|
||||
Push $R7
|
||||
Push $R6
|
||||
Push "false"
|
||||
|
||||
System::Call 'advapi32::OpenSCManagerW(n, n, i ${SC_MANAGER_ALL_ACCESS}) i.R6'
|
||||
${If} $R6 != 0
|
||||
; MpsSvc is the Firewall service on Windows Vista and above.
|
||||
; When opening the service with SERVICE_QUERY_CONFIG the return value will
|
||||
; be 0 if the service is not installed.
|
||||
System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_CONFIG}) i.R7'
|
||||
${If} $R7 != 0
|
||||
System::Call 'advapi32::CloseServiceHandle(i R7) n'
|
||||
; Open the service with SERVICE_QUERY_CONFIG so its status can be queried.
|
||||
System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_STATUS}) i.R7'
|
||||
${Else}
|
||||
; SharedAccess is the Firewall service on Windows XP.
|
||||
; When opening the service with SERVICE_QUERY_CONFIG the return value will
|
||||
; be 0 if the service is not installed.
|
||||
System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_CONFIG}) i.R7'
|
||||
${If} $R7 != 0
|
||||
System::Call 'advapi32::CloseServiceHandle(i R7) n'
|
||||
; Open the service with SERVICE_QUERY_CONFIG so its status can be
|
||||
; queried.
|
||||
System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_STATUS}) i.R7'
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
; Did the calls to OpenServiceW succeed?
|
||||
${If} $R7 != 0
|
||||
System::Call '*(i,i,i,i,i,i,i) i.R9'
|
||||
; Query the current status of the service.
|
||||
System::Call 'advapi32::QueryServiceStatus(i R7, i $R9) i'
|
||||
System::Call '*$R9(i, i.R8)'
|
||||
System::Free $R9
|
||||
System::Call 'advapi32::CloseServiceHandle(i R7) n'
|
||||
IntFmt $R8 "0x%X" $R8
|
||||
${If} $R8 == ${SERVICE_RUNNING}
|
||||
Pop $R9
|
||||
Push "true"
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
System::Call 'advapi32::CloseServiceHandle(i R6) n'
|
||||
${EndIf}
|
||||
|
||||
Exch 1
|
||||
Pop $R6
|
||||
Exch 1
|
||||
Pop $R7
|
||||
Exch 1
|
||||
Pop $R8
|
||||
Exch 1
|
||||
Pop $R9
|
||||
!macroend
|
||||
!define IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
|
||||
!define un.IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
|
||||
|
||||
; Sets this installation as the default browser by setting the registry keys
|
||||
; under HKEY_CURRENT_USER via registry calls and using the AppAssocReg NSIS
|
||||
; plugin for Vista and above. This is a function instead of a macro so it is
|
||||
@ -1619,8 +1685,13 @@ Function FixShortcutAppModelIDs
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
; Helper for adding Firewall exceptions during install and after app update.
|
||||
Function AddFirewallEntries
|
||||
liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
|
||||
${IsFirewallSvcRunning}
|
||||
Pop $0
|
||||
${If} "$0" == "true"
|
||||
liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
; The !ifdef NO_LOG prevents warnings when compiling the installer.nsi due to
|
||||
|
@ -464,8 +464,6 @@ Section "Uninstall"
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
|
||||
liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
|
||||
|
||||
; Refresh desktop icons otherwise the start menu internet item won't be
|
||||
; removed and other ugly things will happen like recreation of the app's
|
||||
; clients registry key by the OS under some conditions.
|
||||
@ -489,6 +487,11 @@ Section "Uninstall"
|
||||
Call un.UninstallServiceIfNotUsed
|
||||
!endif
|
||||
|
||||
${un.IsFirewallSvcRunning}
|
||||
Pop $0
|
||||
${If} "$0" == "true"
|
||||
liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
|
||||
${EndIf}
|
||||
SectionEnd
|
||||
|
||||
################################################################################
|
||||
|
@ -26,6 +26,11 @@ player.transitionNameLabel=Transition
|
||||
# displayed before the animation duration.
|
||||
player.animationDurationLabel=Duration:
|
||||
|
||||
# LOCALIZATION NOTE (player.animationDelayLabel):
|
||||
# This string is displayed in each animation player widget. It is the label
|
||||
# displayed before the animation delay.
|
||||
player.animationDelayLabel=Delay:
|
||||
|
||||
# LOCALIZATION NOTE (player.animationIterationCountLabel):
|
||||
# This string is displayed in each animation player widget. It is the label
|
||||
# displayed before the number of times the animation is set to repeat.
|
||||
|
@ -71,6 +71,16 @@ browserConsoleCmd.commandkey=j
|
||||
# This is the tooltip of the pick button in the toolbox toolbar
|
||||
pickButton.tooltip=Pick an element from the page
|
||||
|
||||
# LOCALIZATION NOTE (sidebar.showAllTabs.label)
|
||||
# This is the label shown in the show all tabs button in the tabbed side
|
||||
# bar, when there's no enough space to show all tabs at once
|
||||
sidebar.showAllTabs.label=…
|
||||
|
||||
# LOCALIZATION NOTE (sidebar.showAllTabs.tooltip)
|
||||
# This is the tooltip shown when hover over the '…' button in the tabbed side
|
||||
# bar, when there's no enough space to show all tabs at once
|
||||
sidebar.showAllTabs.tooltip=All tabs
|
||||
|
||||
# LOCALIZATION NOTE (options.darkTheme.label)
|
||||
# Used as a label for dark theme
|
||||
options.darkTheme.label=Dark theme
|
||||
|
@ -142,6 +142,7 @@ function PreviewController(win, tab) {
|
||||
this.preview = this.win.createTabPreview(this);
|
||||
|
||||
this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
|
||||
this.linkedBrowser.addEventListener("resize", this, false);
|
||||
this.tab.addEventListener("TabAttrModified", this, false);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
|
||||
@ -171,6 +172,7 @@ PreviewController.prototype = {
|
||||
Ci.nsIDOMEventListener]),
|
||||
destroy: function () {
|
||||
this.tab.removeEventListener("TabAttrModified", this, false);
|
||||
this.linkedBrowser.removeEventListener("resize", this, false);
|
||||
this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
|
||||
|
||||
// Break cycles, otherwise we end up leaking the window with everything
|
||||
@ -201,9 +203,17 @@ PreviewController.prototype = {
|
||||
// Resizes the canvasPreview to 0x0, essentially freeing its memory.
|
||||
// updateCanvasPreview() will detect the size mismatch as a resize event
|
||||
// the next time it is called.
|
||||
resetCanvasPreview: function () {
|
||||
this.canvasPreview.width = 0;
|
||||
this.canvasPreview.height = 0;
|
||||
resetCanvasPreview: function () this.resizeCanvasPreview(0, 0),
|
||||
|
||||
resizeCanvasPreview: function (width, height) {
|
||||
this.canvasPreview.width = width;
|
||||
this.canvasPreview.height = height;
|
||||
},
|
||||
|
||||
get wasResizedSinceLastPreview () {
|
||||
let bx = this.linkedBrowser.boxObject;
|
||||
return bx.width != this.canvasPreview.width ||
|
||||
bx.height != this.canvasPreview.height;
|
||||
},
|
||||
|
||||
get zoom() {
|
||||
@ -219,13 +229,15 @@ PreviewController.prototype = {
|
||||
updateCanvasPreview: function () {
|
||||
let win = this.linkedBrowser.contentWindow;
|
||||
let bx = this.linkedBrowser.boxObject;
|
||||
// If we resized then we need to flush layout so that the previews are up
|
||||
// to date. Layout flushing for resizes is deferred for background tabs so
|
||||
// we may need to force it. (bug 526620)
|
||||
let flushLayout = this.wasResizedSinceLastPreview;
|
||||
// Check for resize
|
||||
if (bx.width != this.canvasPreview.width ||
|
||||
bx.height != this.canvasPreview.height) {
|
||||
if (flushLayout) {
|
||||
// Invalidate the entire area and repaint
|
||||
this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight});
|
||||
this.canvasPreview.width = bx.width;
|
||||
this.canvasPreview.height = bx.height;
|
||||
this.resizeCanvasPreview(bx.width, bx.height);
|
||||
}
|
||||
|
||||
// Draw dirty regions
|
||||
@ -233,6 +245,9 @@ PreviewController.prototype = {
|
||||
let scale = this.zoom;
|
||||
|
||||
let flags = this.canvasPreviewFlags;
|
||||
if (flushLayout)
|
||||
flags &= ~Ci.nsIDOMCanvasRenderingContext2D.DRAWWINDOW_DO_NOT_FLUSH;
|
||||
|
||||
// The dirty region may include parts that are offscreen so we clip to the
|
||||
// canvas area.
|
||||
this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight);
|
||||
@ -372,6 +387,19 @@ PreviewController.prototype = {
|
||||
case "TabAttrModified":
|
||||
this.updateTitleAndTooltip();
|
||||
break;
|
||||
case "resize":
|
||||
// We need to invalidate our window's other tabs' previews since layout
|
||||
// due to resizing is delayed for background tabs. Note that this
|
||||
// resize may not be the first after the main window has been resized -
|
||||
// the user may be switching to our tab which forces the resize.
|
||||
this.win.previews.forEach(function (p) {
|
||||
let controller = p.controller.wrappedJSObject;
|
||||
if (controller.wasResizedSinceLastPreview) {
|
||||
controller.resetCanvasPreview();
|
||||
p.invalidate();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -7,7 +7,8 @@
|
||||
there are overrides for each platform in their devedition.css files. */
|
||||
|
||||
:root {
|
||||
--space-above-tabbar: 1px;
|
||||
--tab-toolbar-navbar-overlap: 0px;
|
||||
--space-above-tabbar: 0px;
|
||||
--toolbarbutton-text-shadow: none;
|
||||
--panel-ui-button-background-size: 1px calc(100% - 1px);
|
||||
--panel-ui-button-background-position: 1px 0px;
|
||||
@ -32,9 +33,9 @@
|
||||
--tab-separator-color: #474C50;
|
||||
--tab-selection-color: #f5f7fa;
|
||||
--tab-selection-background-color: #1a4666;
|
||||
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
|
||||
0 8px 3px -5px #2b82bf inset,
|
||||
0 -1px 0 rgba(0,0,0,.2) inset;
|
||||
--tab-selection-box-shadow: 0 2px 0 #D7F1FF inset,
|
||||
0 -2px 0 rgba(0,0,0,.05) inset,
|
||||
0 -1px 0 rgba(0,0,0,.3) inset;
|
||||
--pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, transparent 70%);
|
||||
|
||||
/* Toolbar buttons */
|
||||
@ -92,9 +93,9 @@
|
||||
--tab-separator-color: #C6C6C7;
|
||||
--tab-selection-color: #f5f7fa;
|
||||
--tab-selection-background-color: #4c9ed9;
|
||||
--tab-selection-box-shadow: 0 2px 0 #d7f1ff inset,
|
||||
0 8px 3px -5px #319BDB inset,
|
||||
0 -1px 0 #2A7CB1 inset;
|
||||
--tab-selection-box-shadow: 0 2px 0 #9FDFFF inset,
|
||||
0 -2px 0 rgba(0,0,0,.05) inset,
|
||||
0 -1px 0 rgba(0,0,0,.2) inset;
|
||||
--pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, transparent 16%);
|
||||
|
||||
|
||||
@ -177,7 +178,7 @@
|
||||
#navigator-toolbox > toolbar:not(#TabsToolbar):not(#toolbar-menubar),
|
||||
.browserContainer > findbar,
|
||||
#browser-bottombox {
|
||||
background: var(--chrome-secondary-background-color) !important;
|
||||
background-color: var(--chrome-secondary-background-color) !important;
|
||||
color: var(--chrome-color);
|
||||
}
|
||||
|
||||
@ -227,9 +228,10 @@ window:not([chromehidden~="toolbar"]) #urlbar-wrapper {
|
||||
/* Nav bar specific stuff */
|
||||
#nav-bar {
|
||||
margin-top: 0 !important;
|
||||
border: none !important;
|
||||
border-top: none !important;
|
||||
border-bottom: none !important;
|
||||
border-radius: 0 !important;
|
||||
box-shadow: 0 1px var(--chrome-nav-bar-separator-color) inset !important;
|
||||
box-shadow: 0 -1px var(--chrome-nav-bar-separator-color) !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
@ -288,7 +290,6 @@ searchbar:not([oneoffui]) .search-go-button {
|
||||
}
|
||||
|
||||
.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) > .tab-stack > .tab-content {
|
||||
/* The -2px in `calc` is the height of `tabToolbarNavbarOverlap` plus a 1px offset from the center */
|
||||
background-image: var(--pinned-tab-glow);
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
@ -311,13 +312,6 @@ searchbar:not([oneoffui]) .search-go-button {
|
||||
width: 36px;
|
||||
}
|
||||
|
||||
/* Override @tabToolbarNavbarOverlap@ */
|
||||
#TabsToolbar .toolbarbutton-1,
|
||||
.tabbrowser-arrowscrollbox > .scrollbutton-up,
|
||||
.tabbrowser-arrowscrollbox > .scrollbutton-down {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#TabsToolbar > #new-tab-button:hover,
|
||||
.tabs-newtab-button:hover {
|
||||
/* Important needed because !important is used in browser.css */
|
||||
|
@ -191,7 +191,8 @@
|
||||
|
||||
.theme-toolbar,
|
||||
.devtools-toolbar,
|
||||
.devtools-sidebar-tabs > tabs,
|
||||
.devtools-sidebar-tabs tabs,
|
||||
.devtools-sidebar-alltabs,
|
||||
.CodeMirror-dialog { /* General toolbar styling */
|
||||
color: var(--theme-body-color-alt);
|
||||
background-color: var(--theme-toolbar-background);
|
||||
|
@ -194,7 +194,8 @@
|
||||
|
||||
.theme-toolbar,
|
||||
.devtools-toolbar,
|
||||
.devtools-sidebar-tabs > tabs,
|
||||
.devtools-sidebar-tabs tabs,
|
||||
.devtools-sidebar-alltabs,
|
||||
.CodeMirror-dialog { /* General toolbar styling */
|
||||
color: var(--theme-body-color-alt);
|
||||
background-color: var(--theme-toolbar-background);
|
||||
|
@ -11,7 +11,8 @@
|
||||
|
||||
/* Toolbars */
|
||||
.devtools-toolbar,
|
||||
.devtools-sidebar-tabs > tabs {
|
||||
.devtools-sidebar-tabs tabs,
|
||||
.devtools-sidebar-alltabs {
|
||||
-moz-appearance: none;
|
||||
padding: 0;
|
||||
border-width: 0;
|
||||
@ -416,19 +417,30 @@
|
||||
color: var(--theme-body-color);
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs {
|
||||
.devtools-sidebar-tabs tabs {
|
||||
position: static;
|
||||
font: inherit;
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > .tabs-right,
|
||||
.devtools-sidebar-tabs > tabs > .tabs-left {
|
||||
.devtools-sidebar-alltabs {
|
||||
margin: 0;
|
||||
border-width: 0 0 1px 0;
|
||||
-moz-border-start-width: 1px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.devtools-sidebar-alltabs dropmarker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab {
|
||||
.devtools-sidebar-tabs tabs > .tabs-right,
|
||||
.devtools-sidebar-tabs tabs > .tabs-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs tabs > tab {
|
||||
-moz-appearance: none;
|
||||
/* We want to match the height of a toolbar with a toolbarbutton
|
||||
* First, we need to replicated the padding of toolbar (4px),
|
||||
@ -449,70 +461,70 @@
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.devtools-sidebar-tabs > tabs > tab:first-child {
|
||||
.devtools-sidebar-tabs tabs > tab:first-child {
|
||||
-moz-border-start-width: 0;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab {
|
||||
border-image: @smallSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab:hover {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab:hover {
|
||||
background: hsla(206,37%,4%,.2);
|
||||
border-image: @smallSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab:hover:active {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab:hover:active {
|
||||
background: hsla(206,37%,4%,.4);
|
||||
border-image: @smallSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab {
|
||||
border-image: @solidSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
|
||||
background: hsla(206,37%,4%,.2);
|
||||
border-image: @solidSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover:active {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab[selected] + tab:hover:active {
|
||||
background: hsla(206,37%,4%,.4);
|
||||
border-image: @solidSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected],
|
||||
.theme-dark .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab[selected],
|
||||
.theme-dark .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
|
||||
color: var(--theme-selection-color);
|
||||
background: #1d4f73;
|
||||
border-image: @solidSeparatorDark@ 1 1;
|
||||
}
|
||||
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab {
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab {
|
||||
border-image: @smallSeparatorLight@ 1 1;
|
||||
}
|
||||
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab:hover {
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab:hover {
|
||||
background: #ddd;
|
||||
border-image: @smallSeparatorLight@ 1 1;
|
||||
}
|
||||
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab:hover:active {
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab:hover:active {
|
||||
background: #ddd;
|
||||
border-image: @smallSeparatorLight@ 1 1;
|
||||
}
|
||||
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab {
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab {
|
||||
border-image: @solidSeparatorLight@;
|
||||
}
|
||||
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab[selected] + tab:hover {
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab[selected] + tab:hover {
|
||||
background: #ddd;
|
||||
border-image: @solidSeparatorLight@;
|
||||
}
|
||||
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab[selected],
|
||||
.theme-light .devtools-sidebar-tabs > tabs > tab[selected]:hover:active {
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab[selected],
|
||||
.theme-light .devtools-sidebar-tabs tabs > tab[selected]:hover:active {
|
||||
color: var(--theme-selection-color);
|
||||
background: #4c9ed9;
|
||||
border-image: @solidSeparatorLight@;
|
||||
@ -734,7 +746,7 @@
|
||||
-moz-box-align: center;
|
||||
min-width: 32px;
|
||||
min-height: 24px;
|
||||
max-width: 127px;
|
||||
max-width: 110px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-style: solid;
|
||||
|
@ -10174,6 +10174,9 @@ nsDocShell::InternalLoad(nsIURI * aURI,
|
||||
else
|
||||
srcdoc = NullString();
|
||||
|
||||
mozilla::net::PredictorLearn(aURI, nullptr,
|
||||
nsINetworkPredictor::LEARN_LOAD_TOPLEVEL,
|
||||
this);
|
||||
mozilla::net::PredictorPredict(aURI, nullptr,
|
||||
nsINetworkPredictor::PREDICT_LOAD,
|
||||
this, nullptr);
|
||||
|
@ -322,6 +322,10 @@ public:
|
||||
|
||||
virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
|
||||
|
||||
// GetWindowFromGlobal returns the inner window for this global, if
|
||||
// any, else null.
|
||||
static already_AddRefed<nsPIDOMWindow> GetWindowFromGlobal(JSObject* aGlobal);
|
||||
|
||||
#ifdef MOZ_EME
|
||||
already_AddRefed<Promise>
|
||||
RequestMediaKeySystemAccess(const nsAString& aKeySystem,
|
||||
@ -334,9 +338,6 @@ private:
|
||||
|
||||
bool CheckPermission(const char* type);
|
||||
static bool CheckPermission(nsPIDOMWindow* aWindow, const char* aType);
|
||||
// GetWindowFromGlobal returns the inner window for this global, if
|
||||
// any, else null.
|
||||
static already_AddRefed<nsPIDOMWindow> GetWindowFromGlobal(JSObject* aGlobal);
|
||||
|
||||
nsRefPtr<nsMimeTypeArray> mMimeTypes;
|
||||
nsRefPtr<nsPluginArray> mPlugins;
|
||||
|
760
dom/broadcastchannel/BroadcastChannel.cpp
Normal file
760
dom/broadcastchannel/BroadcastChannel.cpp
Normal file
@ -0,0 +1,760 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BroadcastChannel.h"
|
||||
#include "BroadcastChannelChild.h"
|
||||
#include "mozilla/dom/BroadcastChannelBinding.h"
|
||||
#include "mozilla/dom/Navigator.h"
|
||||
#include "mozilla/dom/StructuredCloneUtils.h"
|
||||
#include "mozilla/ipc/BackgroundChild.h"
|
||||
#include "mozilla/ipc/BackgroundUtils.h"
|
||||
#include "mozilla/ipc/PBackgroundChild.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "WorkerPrivate.h"
|
||||
#include "WorkerRunnable.h"
|
||||
|
||||
#include "nsIAppsService.h"
|
||||
#include "nsIDocument.h"
|
||||
#include "nsIScriptSecurityManager.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsISupportsPrimitives.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#undef PostMessage
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
namespace dom {
|
||||
|
||||
using namespace workers;
|
||||
|
||||
class BroadcastChannelMessage MOZ_FINAL
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(BroadcastChannelMessage)
|
||||
|
||||
JSAutoStructuredCloneBuffer mBuffer;
|
||||
StructuredCloneClosure mClosure;
|
||||
|
||||
BroadcastChannelMessage()
|
||||
{ }
|
||||
|
||||
private:
|
||||
~BroadcastChannelMessage()
|
||||
{ }
|
||||
};
|
||||
|
||||
namespace {
|
||||
|
||||
void
|
||||
GetOrigin(nsIPrincipal* aPrincipal, nsAString& aOrigin, ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(aPrincipal);
|
||||
|
||||
uint16_t appStatus = aPrincipal->GetAppStatus();
|
||||
|
||||
if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED) {
|
||||
nsAutoString tmp;
|
||||
aRv = nsContentUtils::GetUTFOrigin(aPrincipal, tmp);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return;
|
||||
}
|
||||
|
||||
aOrigin = tmp;
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t appId = aPrincipal->GetAppId();
|
||||
|
||||
// If we are in "app code", use manifest URL as unique origin since
|
||||
// multiple apps can share the same origin but not same broadcast messages.
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIAppsService> appsService =
|
||||
do_GetService("@mozilla.org/AppsService;1", &rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
aRv.Throw(rv);
|
||||
return;
|
||||
}
|
||||
|
||||
appsService->GetManifestURLByLocalId(appId, aOrigin);
|
||||
}
|
||||
|
||||
nsIPrincipal*
|
||||
GetPrincipalFromWorkerPrivate(WorkerPrivate* aWorkerPrivate)
|
||||
{
|
||||
nsIPrincipal* principal = aWorkerPrivate->GetPrincipal();
|
||||
if (principal) {
|
||||
return principal;
|
||||
}
|
||||
|
||||
// Walk up to our containing page
|
||||
WorkerPrivate* wp = aWorkerPrivate;
|
||||
while (wp->GetParent()) {
|
||||
wp = wp->GetParent();
|
||||
}
|
||||
|
||||
return wp->GetPrincipal();
|
||||
}
|
||||
|
||||
class InitializeRunnable MOZ_FINAL : public WorkerMainThreadRunnable
|
||||
{
|
||||
public:
|
||||
InitializeRunnable(WorkerPrivate* aWorkerPrivate, nsAString& aOrigin,
|
||||
PrincipalInfo& aPrincipalInfo, ErrorResult& aRv)
|
||||
: WorkerMainThreadRunnable(aWorkerPrivate)
|
||||
, mWorkerPrivate(GetCurrentThreadWorkerPrivate())
|
||||
, mOrigin(aOrigin)
|
||||
, mPrincipalInfo(aPrincipalInfo)
|
||||
, mRv(aRv)
|
||||
{
|
||||
MOZ_ASSERT(mWorkerPrivate);
|
||||
}
|
||||
|
||||
bool MainThreadRun() MOZ_OVERRIDE
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
nsIPrincipal* principal = GetPrincipalFromWorkerPrivate(mWorkerPrivate);
|
||||
if (!principal) {
|
||||
mRv.Throw(NS_ERROR_FAILURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isNullPrincipal;
|
||||
mRv = principal->GetIsNullPrincipal(&isNullPrincipal);
|
||||
if (NS_WARN_IF(mRv.Failed())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(isNullPrincipal)) {
|
||||
mRv.Throw(NS_ERROR_FAILURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
mRv = PrincipalToPrincipalInfo(principal, &mPrincipalInfo);
|
||||
if (NS_WARN_IF(mRv.Failed())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GetOrigin(principal, mOrigin, mRv);
|
||||
if (NS_WARN_IF(mRv.Failed())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Walk up to our containing page
|
||||
WorkerPrivate* wp = mWorkerPrivate;
|
||||
while (wp->GetParent()) {
|
||||
wp = wp->GetParent();
|
||||
}
|
||||
|
||||
// Window doesn't exist for some kind of workers (eg: SharedWorkers)
|
||||
nsPIDOMWindow* window = wp->GetWindow();
|
||||
if (!window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
nsIDocument* doc = window->GetExtantDoc();
|
||||
// No bfcache when BroadcastChannel is used.
|
||||
if (doc) {
|
||||
doc->DisallowBFCaching();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
WorkerPrivate* mWorkerPrivate;
|
||||
nsAString& mOrigin;
|
||||
PrincipalInfo& mPrincipalInfo;
|
||||
ErrorResult& mRv;
|
||||
};
|
||||
|
||||
class PostMessageRunnable MOZ_FINAL : public nsICancelableRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
PostMessageRunnable(BroadcastChannelChild* aActor,
|
||||
BroadcastChannelMessage* aData)
|
||||
: mActor(aActor)
|
||||
, mData(aData)
|
||||
{
|
||||
MOZ_ASSERT(mActor);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(mActor);
|
||||
if (mActor->IsActorDestroyed()) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
ClonedMessageData message;
|
||||
|
||||
SerializedStructuredCloneBuffer& buffer = message.data();
|
||||
buffer.data = mData->mBuffer.data();
|
||||
buffer.dataLength = mData->mBuffer.nbytes();
|
||||
|
||||
PBackgroundChild* backgroundManager = mActor->Manager();
|
||||
MOZ_ASSERT(backgroundManager);
|
||||
|
||||
const nsTArray<nsRefPtr<File>>& blobs = mData->mClosure.mBlobs;
|
||||
|
||||
if (!blobs.IsEmpty()) {
|
||||
message.blobsChild().SetCapacity(blobs.Length());
|
||||
|
||||
for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
|
||||
PBlobChild* blobChild =
|
||||
BackgroundChild::GetOrCreateActorForBlob(backgroundManager, blobs[i]);
|
||||
MOZ_ASSERT(blobChild);
|
||||
|
||||
message.blobsChild().AppendElement(blobChild);
|
||||
}
|
||||
}
|
||||
|
||||
mActor->SendPostMessage(message);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP Cancel()
|
||||
{
|
||||
mActor = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~PostMessageRunnable() {}
|
||||
|
||||
nsRefPtr<BroadcastChannelChild> mActor;
|
||||
nsRefPtr<BroadcastChannelMessage> mData;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(PostMessageRunnable, nsICancelableRunnable, nsIRunnable)
|
||||
|
||||
class CloseRunnable MOZ_FINAL : public nsICancelableRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
explicit CloseRunnable(BroadcastChannel* aBC)
|
||||
: mBC(aBC)
|
||||
{
|
||||
MOZ_ASSERT(mBC);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
mBC->Shutdown();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP Cancel()
|
||||
{
|
||||
mBC = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~CloseRunnable() {}
|
||||
|
||||
nsRefPtr<BroadcastChannel> mBC;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(CloseRunnable, nsICancelableRunnable, nsIRunnable)
|
||||
|
||||
class TeardownRunnable MOZ_FINAL : public nsICancelableRunnable
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
explicit TeardownRunnable(BroadcastChannelChild* aActor)
|
||||
: mActor(aActor)
|
||||
{
|
||||
MOZ_ASSERT(mActor);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP Run()
|
||||
{
|
||||
MOZ_ASSERT(mActor);
|
||||
if (!mActor->IsActorDestroyed()) {
|
||||
mActor->SendClose();
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP Cancel()
|
||||
{
|
||||
mActor = nullptr;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
~TeardownRunnable() {}
|
||||
|
||||
nsRefPtr<BroadcastChannelChild> mActor;
|
||||
};
|
||||
|
||||
NS_IMPL_ISUPPORTS(TeardownRunnable, nsICancelableRunnable, nsIRunnable)
|
||||
|
||||
class BroadcastChannelFeature MOZ_FINAL : public workers::WorkerFeature
|
||||
{
|
||||
BroadcastChannel* mChannel;
|
||||
|
||||
public:
|
||||
explicit BroadcastChannelFeature(BroadcastChannel* aChannel)
|
||||
: mChannel(aChannel)
|
||||
{
|
||||
MOZ_COUNT_CTOR(BroadcastChannelFeature);
|
||||
}
|
||||
|
||||
virtual bool Notify(JSContext* aCx, workers::Status aStatus) MOZ_OVERRIDE
|
||||
{
|
||||
if (aStatus >= Canceling) {
|
||||
mChannel->Shutdown();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
~BroadcastChannelFeature()
|
||||
{
|
||||
MOZ_COUNT_DTOR(BroadcastChannelFeature);
|
||||
}
|
||||
};
|
||||
|
||||
class PrefEnabledRunnable MOZ_FINAL : public WorkerMainThreadRunnable
|
||||
{
|
||||
public:
|
||||
explicit PrefEnabledRunnable(WorkerPrivate* aWorkerPrivate)
|
||||
: WorkerMainThreadRunnable(aWorkerPrivate)
|
||||
, mEnabled(false)
|
||||
{ }
|
||||
|
||||
bool MainThreadRun() MOZ_OVERRIDE
|
||||
{
|
||||
AssertIsOnMainThread();
|
||||
mEnabled = Preferences::GetBool("dom.broadcastChannel.enabled", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsEnabled() const
|
||||
{
|
||||
return mEnabled;
|
||||
}
|
||||
|
||||
private:
|
||||
bool mEnabled;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
/* static */ bool
|
||||
BroadcastChannel::IsEnabled(JSContext* aCx, JSObject* aGlobal)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
return Preferences::GetBool("dom.broadcastChannel.enabled", false);
|
||||
}
|
||||
|
||||
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
workerPrivate->AssertIsOnWorkerThread();
|
||||
|
||||
nsRefPtr<PrefEnabledRunnable> runnable =
|
||||
new PrefEnabledRunnable(workerPrivate);
|
||||
runnable->Dispatch(workerPrivate->GetJSContext());
|
||||
|
||||
return runnable->IsEnabled();
|
||||
}
|
||||
|
||||
BroadcastChannel::BroadcastChannel(nsPIDOMWindow* aWindow,
|
||||
const PrincipalInfo& aPrincipalInfo,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aChannel)
|
||||
: DOMEventTargetHelper(aWindow)
|
||||
, mWorkerFeature(nullptr)
|
||||
, mPrincipalInfo(new PrincipalInfo(aPrincipalInfo))
|
||||
, mOrigin(aOrigin)
|
||||
, mChannel(aChannel)
|
||||
, mIsKeptAlive(false)
|
||||
, mInnerID(0)
|
||||
, mState(StateActive)
|
||||
{
|
||||
// Window can be null in workers
|
||||
}
|
||||
|
||||
BroadcastChannel::~BroadcastChannel()
|
||||
{
|
||||
Shutdown();
|
||||
MOZ_ASSERT(!mWorkerFeature);
|
||||
}
|
||||
|
||||
JSObject*
|
||||
BroadcastChannel::WrapObject(JSContext* aCx)
|
||||
{
|
||||
return BroadcastChannelBinding::Wrap(aCx, this);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<BroadcastChannel>
|
||||
BroadcastChannel::Constructor(const GlobalObject& aGlobal,
|
||||
const nsAString& aChannel,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
// Window is null in workers.
|
||||
|
||||
nsAutoString origin;
|
||||
PrincipalInfo principalInfo;
|
||||
WorkerPrivate* workerPrivate = nullptr;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
nsCOMPtr<nsIGlobalObject> incumbent = mozilla::dom::GetIncumbentGlobal();
|
||||
|
||||
if (!incumbent) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIPrincipal* principal = incumbent->PrincipalOrNull();
|
||||
if (!principal) {
|
||||
aRv.Throw(NS_ERROR_UNEXPECTED);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool isNullPrincipal;
|
||||
aRv = principal->GetIsNullPrincipal(&isNullPrincipal);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(isNullPrincipal)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GetOrigin(principal, origin, aRv);
|
||||
|
||||
aRv = PrincipalToPrincipalInfo(principal, &principalInfo);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsIDocument* doc = window->GetExtantDoc();
|
||||
// No bfcache when BroadcastChannel is used.
|
||||
if (doc) {
|
||||
doc->DisallowBFCaching();
|
||||
}
|
||||
} else {
|
||||
JSContext* cx = aGlobal.Context();
|
||||
workerPrivate = GetWorkerPrivateFromContext(cx);
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
|
||||
nsRefPtr<InitializeRunnable> runnable =
|
||||
new InitializeRunnable(workerPrivate, origin, principalInfo, aRv);
|
||||
runnable->Dispatch(cx);
|
||||
}
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<BroadcastChannel> bc =
|
||||
new BroadcastChannel(window, principalInfo, origin, aChannel);
|
||||
|
||||
// Register this component to PBackground.
|
||||
PBackgroundChild* actor = BackgroundChild::GetForCurrentThread();
|
||||
if (actor) {
|
||||
bc->ActorCreated(actor);
|
||||
} else {
|
||||
BackgroundChild::GetOrCreateForCurrentThread(bc);
|
||||
}
|
||||
|
||||
if (!workerPrivate) {
|
||||
MOZ_ASSERT(window);
|
||||
MOZ_ASSERT(window->IsInnerWindow());
|
||||
bc->mInnerID = window->WindowID();
|
||||
|
||||
// Register as observer for inner-window-destroyed.
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->AddObserver(bc, "inner-window-destroyed", false);
|
||||
}
|
||||
} else {
|
||||
bc->mWorkerFeature = new BroadcastChannelFeature(bc);
|
||||
JSContext* cx = workerPrivate->GetJSContext();
|
||||
if (NS_WARN_IF(!workerPrivate->AddFeature(cx, bc->mWorkerFeature))) {
|
||||
NS_WARNING("Failed to register the BroadcastChannel worker feature.");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return bc.forget();
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (mState != StateActive) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return;
|
||||
}
|
||||
|
||||
PostMessageInternal(aCx, aMessage, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::PostMessageInternal(JSContext* aCx,
|
||||
JS::Handle<JS::Value> aMessage,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<BroadcastChannelMessage> data = new BroadcastChannelMessage();
|
||||
|
||||
if (!WriteStructuredClone(aCx, aMessage, data->mBuffer, data->mClosure)) {
|
||||
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
const nsTArray<nsRefPtr<File>>& blobs = data->mClosure.mBlobs;
|
||||
for (uint32_t i = 0, len = blobs.Length(); i < len; ++i) {
|
||||
if (!blobs[i]->Impl()->MayBeClonedToOtherThreads()) {
|
||||
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PostMessageData(data);
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::PostMessageData(BroadcastChannelMessage* aData)
|
||||
{
|
||||
if (mActor) {
|
||||
nsRefPtr<PostMessageRunnable> runnable =
|
||||
new PostMessageRunnable(mActor, aData);
|
||||
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
|
||||
NS_WARNING("Failed to dispatch to the current thread!");
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mPendingMessages.AppendElement(aData);
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::Close()
|
||||
{
|
||||
if (mState != StateActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPendingMessages.IsEmpty()) {
|
||||
// We cannot call Shutdown() immediatelly because we could have some
|
||||
// postMessage runnable already dispatched. Instead, we change the state to
|
||||
// StateClosed and we shutdown the actor asynchrounsly.
|
||||
|
||||
mState = StateClosed;
|
||||
nsRefPtr<CloseRunnable> runnable = new CloseRunnable(this);
|
||||
|
||||
if (NS_FAILED(NS_DispatchToCurrentThread(runnable))) {
|
||||
NS_WARNING("Failed to dispatch to the current thread!");
|
||||
}
|
||||
} else {
|
||||
MOZ_ASSERT(!mActor);
|
||||
mState = StateClosing;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::ActorFailed()
|
||||
{
|
||||
MOZ_CRASH("Failed to create a PBackgroundChild actor!");
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::ActorCreated(PBackgroundChild* aActor)
|
||||
{
|
||||
MOZ_ASSERT(aActor);
|
||||
|
||||
if (mState == StateClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
PBroadcastChannelChild* actor =
|
||||
aActor->SendPBroadcastChannelConstructor(*mPrincipalInfo, mOrigin, mChannel);
|
||||
|
||||
mActor = static_cast<BroadcastChannelChild*>(actor);
|
||||
MOZ_ASSERT(mActor);
|
||||
|
||||
mActor->SetParent(this);
|
||||
|
||||
// Flush pending messages.
|
||||
for (uint32_t i = 0; i < mPendingMessages.Length(); ++i) {
|
||||
PostMessageData(mPendingMessages[i]);
|
||||
}
|
||||
|
||||
mPendingMessages.Clear();
|
||||
|
||||
if (mState == StateClosing) {
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::Shutdown()
|
||||
{
|
||||
mState = StateClosed;
|
||||
|
||||
// If shutdown() is called we have to release the reference if we still keep
|
||||
// it.
|
||||
if (mIsKeptAlive) {
|
||||
mIsKeptAlive = false;
|
||||
Release();
|
||||
}
|
||||
|
||||
if (mWorkerFeature) {
|
||||
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
||||
workerPrivate->RemoveFeature(workerPrivate->GetJSContext(), mWorkerFeature);
|
||||
mWorkerFeature = nullptr;
|
||||
}
|
||||
|
||||
if (mActor) {
|
||||
mActor->SetParent(nullptr);
|
||||
|
||||
nsRefPtr<TeardownRunnable> runnable = new TeardownRunnable(mActor);
|
||||
NS_DispatchToCurrentThread(runnable);
|
||||
|
||||
mActor = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
EventHandlerNonNull*
|
||||
BroadcastChannel::GetOnmessage()
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
return GetEventHandler(nsGkAtoms::onmessage, EmptyString());
|
||||
}
|
||||
return GetEventHandler(nullptr, NS_LITERAL_STRING("message"));
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::SetOnmessage(EventHandlerNonNull* aCallback)
|
||||
{
|
||||
if (NS_IsMainThread()) {
|
||||
SetEventHandler(nsGkAtoms::onmessage, EmptyString(), aCallback);
|
||||
} else {
|
||||
SetEventHandler(nullptr, NS_LITERAL_STRING("message"), aCallback);
|
||||
}
|
||||
|
||||
UpdateMustKeepAlive();
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::AddEventListener(const nsAString& aType,
|
||||
EventListener* aCallback,
|
||||
bool aCapture,
|
||||
const dom::Nullable<bool>& aWantsUntrusted,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
DOMEventTargetHelper::AddEventListener(aType, aCallback, aCapture,
|
||||
aWantsUntrusted, aRv);
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateMustKeepAlive();
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::RemoveEventListener(const nsAString& aType,
|
||||
EventListener* aCallback,
|
||||
bool aCapture,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
DOMEventTargetHelper::RemoveEventListener(aType, aCallback, aCapture, aRv);
|
||||
|
||||
if (aRv.Failed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateMustKeepAlive();
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannel::UpdateMustKeepAlive()
|
||||
{
|
||||
bool toKeepAlive = HasListenersFor(NS_LITERAL_STRING("message"));
|
||||
if (toKeepAlive == mIsKeptAlive) {
|
||||
return;
|
||||
}
|
||||
|
||||
mIsKeptAlive = toKeepAlive;
|
||||
|
||||
if (toKeepAlive) {
|
||||
AddRef();
|
||||
} else {
|
||||
Release();
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
BroadcastChannel::Observe(nsISupports* aSubject, const char* aTopic,
|
||||
const char16_t* aData)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(!strcmp(aTopic, "inner-window-destroyed"));
|
||||
|
||||
// If the window is destroyed we have to release the reference that we are
|
||||
// keeping.
|
||||
if (!mIsKeptAlive) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
|
||||
NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
|
||||
|
||||
uint64_t innerID;
|
||||
nsresult rv = wrapper->GetData(&innerID);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (innerID == mInnerID) {
|
||||
nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
|
||||
if (obs) {
|
||||
obs->RemoveObserver(this, "inner-window-destroyed");
|
||||
}
|
||||
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_CLASS(BroadcastChannel)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BroadcastChannel,
|
||||
DOMEventTargetHelper)
|
||||
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BroadcastChannel,
|
||||
DOMEventTargetHelper)
|
||||
tmp->Shutdown();
|
||||
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BroadcastChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
||||
NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(BroadcastChannel, DOMEventTargetHelper)
|
||||
NS_IMPL_RELEASE_INHERITED(BroadcastChannel, DOMEventTargetHelper)
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
130
dom/broadcastchannel/BroadcastChannel.h
Normal file
130
dom/broadcastchannel/BroadcastChannel.h
Normal file
@ -0,0 +1,130 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_dom_BroadcastChannel_h
|
||||
#define mozilla_dom_BroadcastChannel_h
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/DOMEventTargetHelper.h"
|
||||
#include "nsIIPCBackgroundChildCreateCallback.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsTArray.h"
|
||||
#include "nsRefPtr.h"
|
||||
|
||||
class nsPIDOMWindow;
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
class PrincipalInfo;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
namespace workers {
|
||||
class WorkerFeature;
|
||||
}
|
||||
|
||||
class BroadcastChannelChild;
|
||||
class BroadcastChannelMessage;
|
||||
|
||||
class BroadcastChannel MOZ_FINAL
|
||||
: public DOMEventTargetHelper
|
||||
, public nsIIPCBackgroundChildCreateCallback
|
||||
, public nsIObserver
|
||||
{
|
||||
NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
|
||||
NS_DECL_NSIOBSERVER
|
||||
|
||||
typedef mozilla::ipc::PrincipalInfo PrincipalInfo;
|
||||
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BroadcastChannel,
|
||||
DOMEventTargetHelper)
|
||||
|
||||
static bool IsEnabled(JSContext* aCx, JSObject* aGlobal);
|
||||
|
||||
virtual JSObject*
|
||||
WrapObject(JSContext* aCx) MOZ_OVERRIDE;
|
||||
|
||||
static already_AddRefed<BroadcastChannel>
|
||||
Constructor(const GlobalObject& aGlobal, const nsAString& aChannel,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void GetName(nsAString& aName) const
|
||||
{
|
||||
aName = mChannel;
|
||||
}
|
||||
|
||||
void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void Close();
|
||||
|
||||
EventHandlerNonNull* GetOnmessage();
|
||||
void SetOnmessage(EventHandlerNonNull* aCallback);
|
||||
|
||||
using nsIDOMEventTarget::AddEventListener;
|
||||
using nsIDOMEventTarget::RemoveEventListener;
|
||||
|
||||
virtual void AddEventListener(const nsAString& aType,
|
||||
EventListener* aCallback,
|
||||
bool aCapture,
|
||||
const Nullable<bool>& aWantsUntrusted,
|
||||
ErrorResult& aRv) MOZ_OVERRIDE;
|
||||
virtual void RemoveEventListener(const nsAString& aType,
|
||||
EventListener* aCallback,
|
||||
bool aCapture,
|
||||
ErrorResult& aRv) MOZ_OVERRIDE;
|
||||
|
||||
void Shutdown();
|
||||
|
||||
bool IsClosed() const
|
||||
{
|
||||
return mState != StateActive;
|
||||
}
|
||||
|
||||
private:
|
||||
BroadcastChannel(nsPIDOMWindow* aWindow,
|
||||
const PrincipalInfo& aPrincipalInfo,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aChannel);
|
||||
|
||||
~BroadcastChannel();
|
||||
|
||||
void PostMessageData(BroadcastChannelMessage* aData);
|
||||
|
||||
void PostMessageInternal(JSContext* aCx, JS::Handle<JS::Value> aMessage,
|
||||
ErrorResult& aRv);
|
||||
|
||||
void UpdateMustKeepAlive();
|
||||
|
||||
nsRefPtr<BroadcastChannelChild> mActor;
|
||||
nsTArray<nsRefPtr<BroadcastChannelMessage>> mPendingMessages;
|
||||
|
||||
nsAutoPtr<workers::WorkerFeature> mWorkerFeature;
|
||||
|
||||
nsAutoPtr<PrincipalInfo> mPrincipalInfo;
|
||||
|
||||
nsString mOrigin;
|
||||
nsString mChannel;
|
||||
|
||||
bool mIsKeptAlive;
|
||||
|
||||
uint64_t mInnerID;
|
||||
|
||||
enum {
|
||||
StateActive,
|
||||
StateClosing,
|
||||
StateClosed
|
||||
} mState;
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_dom_BroadcastChannel_h
|
133
dom/broadcastchannel/BroadcastChannelChild.cpp
Normal file
133
dom/broadcastchannel/BroadcastChannelChild.cpp
Normal file
@ -0,0 +1,133 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BroadcastChannelChild.h"
|
||||
#include "BroadcastChannel.h"
|
||||
#include "jsapi.h"
|
||||
#include "mozilla/dom/ipc/BlobChild.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/MessageEvent.h"
|
||||
#include "mozilla/dom/MessageEventBinding.h"
|
||||
#include "mozilla/dom/StructuredCloneUtils.h"
|
||||
#include "mozilla/dom/WorkerPrivate.h"
|
||||
#include "mozilla/dom/WorkerScope.h"
|
||||
#include "mozilla/dom/ScriptSettings.h"
|
||||
#include "mozilla/ipc/PBackgroundChild.h"
|
||||
#include "WorkerPrivate.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
namespace dom {
|
||||
|
||||
using namespace workers;
|
||||
|
||||
BroadcastChannelChild::BroadcastChannelChild(const nsAString& aOrigin,
|
||||
const nsAString& aChannel)
|
||||
: mOrigin(aOrigin)
|
||||
, mChannel(aChannel)
|
||||
, mActorDestroyed(false)
|
||||
{
|
||||
}
|
||||
|
||||
BroadcastChannelChild::~BroadcastChannelChild()
|
||||
{
|
||||
MOZ_ASSERT(!mBC);
|
||||
}
|
||||
|
||||
bool
|
||||
BroadcastChannelChild::RecvNotify(const ClonedMessageData& aData)
|
||||
{
|
||||
// Make sure to retrieve all blobs from the message before returning to avoid
|
||||
// leaking their actors.
|
||||
nsTArray<nsRefPtr<File>> files;
|
||||
if (!aData.blobsChild().IsEmpty()) {
|
||||
files.SetCapacity(aData.blobsChild().Length());
|
||||
|
||||
for (uint32_t i = 0, len = aData.blobsChild().Length(); i < len; ++i) {
|
||||
nsRefPtr<FileImpl> impl =
|
||||
static_cast<BlobChild*>(aData.blobsChild()[i])->GetBlobImpl();
|
||||
|
||||
nsRefPtr<File> file = new File(mBC ? mBC->GetOwner() : nullptr, impl);
|
||||
files.AppendElement(file);
|
||||
}
|
||||
}
|
||||
|
||||
nsCOMPtr<DOMEventTargetHelper> helper = mBC;
|
||||
nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(helper);
|
||||
|
||||
// This object has been already closed by content or is going to be deleted
|
||||
// soon. No notify is required.
|
||||
if (!eventTarget || mBC->IsClosed()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// CheckInnerWindowCorrectness can be used also without a window when
|
||||
// BroadcastChannel is running in a worker. In this case, it's a NOP.
|
||||
if (NS_FAILED(mBC->CheckInnerWindowCorrectness())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
nsCOMPtr<nsIGlobalObject> globalObject;
|
||||
|
||||
if (NS_IsMainThread()) {
|
||||
globalObject = do_QueryInterface(mBC->GetParentObject());
|
||||
} else {
|
||||
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
|
||||
MOZ_ASSERT(workerPrivate);
|
||||
globalObject = workerPrivate->GlobalScope();
|
||||
}
|
||||
|
||||
if (!globalObject || !jsapi.Init(globalObject)) {
|
||||
NS_WARNING("Failed to initialize AutoJSAPI object.");
|
||||
return true;
|
||||
}
|
||||
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
const SerializedStructuredCloneBuffer& buffer = aData.data();
|
||||
StructuredCloneData cloneData;
|
||||
cloneData.mData = buffer.data;
|
||||
cloneData.mDataLength = buffer.dataLength;
|
||||
cloneData.mClosure.mBlobs.SwapElements(files);
|
||||
|
||||
JS::Rooted<JS::Value> value(cx, JS::NullValue());
|
||||
if (cloneData.mDataLength && !ReadStructuredClone(cx, cloneData, &value)) {
|
||||
JS_ClearPendingException(cx);
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedDictionary<MessageEventInit> init(cx);
|
||||
init.mBubbles = false;
|
||||
init.mCancelable = false;
|
||||
init.mOrigin.Construct(mOrigin);
|
||||
init.mData = value;
|
||||
|
||||
ErrorResult rv;
|
||||
nsRefPtr<MessageEvent> event =
|
||||
MessageEvent::Constructor(mBC, NS_LITERAL_STRING("message"), init, rv);
|
||||
if (rv.Failed()) {
|
||||
NS_WARNING("Failed to create a MessageEvent object.");
|
||||
return true;
|
||||
}
|
||||
|
||||
event->SetTrusted(true);
|
||||
|
||||
bool status;
|
||||
mBC->DispatchEvent(static_cast<Event*>(event.get()), &status);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannelChild::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
mActorDestroyed = true;
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
60
dom/broadcastchannel/BroadcastChannelChild.h
Normal file
60
dom/broadcastchannel/BroadcastChannelChild.h
Normal file
@ -0,0 +1,60 @@
|
||||
/* 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_BroadcastChannelChild_h
|
||||
#define mozilla_dom_BroadcastChannelChild_h
|
||||
|
||||
#include "mozilla/dom/PBroadcastChannelChild.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
class BackgroundChildImpl;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
class BroadcastChannel;
|
||||
|
||||
class BroadcastChannelChild MOZ_FINAL : public PBroadcastChannelChild
|
||||
{
|
||||
friend class mozilla::ipc::BackgroundChildImpl;
|
||||
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(BroadcastChannelChild)
|
||||
|
||||
void SetParent(BroadcastChannel* aBC)
|
||||
{
|
||||
mBC = aBC;
|
||||
}
|
||||
|
||||
virtual bool RecvNotify(const ClonedMessageData& aData) MOZ_OVERRIDE;
|
||||
|
||||
bool IsActorDestroyed() const
|
||||
{
|
||||
return mActorDestroyed;
|
||||
}
|
||||
|
||||
private:
|
||||
BroadcastChannelChild(const nsAString& aOrigin,
|
||||
const nsAString& aChannel);
|
||||
|
||||
~BroadcastChannelChild();
|
||||
|
||||
void ActorDestroy(ActorDestroyReason aWhy);
|
||||
|
||||
// This raw pointer is actually the parent object.
|
||||
// It's set to null when the parent object is deleted.
|
||||
BroadcastChannel* mBC;
|
||||
|
||||
nsString mOrigin;
|
||||
nsString mChannel;
|
||||
|
||||
bool mActorDestroyed;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_BroadcastChannelChild_h
|
113
dom/broadcastchannel/BroadcastChannelParent.cpp
Normal file
113
dom/broadcastchannel/BroadcastChannelParent.cpp
Normal file
@ -0,0 +1,113 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BroadcastChannelParent.h"
|
||||
#include "BroadcastChannelService.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#include "mozilla/dom/ipc/BlobParent.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
#include "mozilla/unused.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
namespace dom {
|
||||
|
||||
BroadcastChannelParent::BroadcastChannelParent(
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aChannel)
|
||||
: mService(BroadcastChannelService::GetOrCreate())
|
||||
, mOrigin(aOrigin)
|
||||
, mChannel(aChannel)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
mService->RegisterActor(this);
|
||||
}
|
||||
|
||||
BroadcastChannelParent::~BroadcastChannelParent()
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
}
|
||||
|
||||
bool
|
||||
BroadcastChannelParent::RecvPostMessage(const ClonedMessageData& aData)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
if (NS_WARN_IF(!mService)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mService->PostMessage(this, aData, mOrigin, mChannel);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
BroadcastChannelParent::RecvClose()
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
if (NS_WARN_IF(!mService)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mService->UnregisterActor(this);
|
||||
mService = nullptr;
|
||||
|
||||
unused << Send__delete__(this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannelParent::ActorDestroy(ActorDestroyReason aWhy)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
if (mService) {
|
||||
// This object is about to be released and with it, also mService will be
|
||||
// released too.
|
||||
mService->UnregisterActor(this);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannelParent::CheckAndDeliver(const ClonedMessageData& aData,
|
||||
const nsString& aOrigin,
|
||||
const nsString& aChannel)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
if (aOrigin == mOrigin && aChannel == mChannel) {
|
||||
// We need to duplicate data only if we have blobs.
|
||||
if (aData.blobsParent().IsEmpty()) {
|
||||
unused << SendNotify(aData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Duplicate the data for this parent.
|
||||
ClonedMessageData newData(aData);
|
||||
|
||||
// Ricreate the BlobParent for this new message.
|
||||
for (uint32_t i = 0, len = newData.blobsParent().Length(); i < len; ++i) {
|
||||
nsRefPtr<FileImpl> impl =
|
||||
static_cast<BlobParent*>(newData.blobsParent()[i])->GetBlobImpl();
|
||||
|
||||
PBlobParent* blobParent =
|
||||
BackgroundParent::GetOrCreateActorForBlobImpl(Manager(), impl);
|
||||
if (!blobParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
newData.blobsParent()[i] = blobParent;
|
||||
}
|
||||
|
||||
unused << SendNotify(newData);
|
||||
}
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
49
dom/broadcastchannel/BroadcastChannelParent.h
Normal file
49
dom/broadcastchannel/BroadcastChannelParent.h
Normal file
@ -0,0 +1,49 @@
|
||||
/* 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_BroadcastChannelParent_h
|
||||
#define mozilla_dom_BroadcastChannelParent_h
|
||||
|
||||
#include "mozilla/dom/PBroadcastChannelParent.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
namespace ipc {
|
||||
class BackgroundParentImpl;
|
||||
}
|
||||
|
||||
namespace dom {
|
||||
|
||||
class BroadcastChannelService;
|
||||
|
||||
class BroadcastChannelParent MOZ_FINAL : public PBroadcastChannelParent
|
||||
{
|
||||
friend class mozilla::ipc::BackgroundParentImpl;
|
||||
|
||||
public:
|
||||
void CheckAndDeliver(const ClonedMessageData& aData,
|
||||
const nsString& aOrigin,
|
||||
const nsString& aChannel);
|
||||
|
||||
private:
|
||||
BroadcastChannelParent(const nsAString& aOrigin,
|
||||
const nsAString& aChannel);
|
||||
~BroadcastChannelParent();
|
||||
|
||||
virtual bool
|
||||
RecvPostMessage(const ClonedMessageData& aData) MOZ_OVERRIDE;
|
||||
|
||||
virtual bool RecvClose() MOZ_OVERRIDE;
|
||||
|
||||
virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
|
||||
|
||||
nsRefPtr<BroadcastChannelService> mService;
|
||||
nsString mOrigin;
|
||||
nsString mChannel;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_BroadcastChannelParent_h
|
138
dom/broadcastchannel/BroadcastChannelService.cpp
Normal file
138
dom/broadcastchannel/BroadcastChannelService.cpp
Normal file
@ -0,0 +1,138 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "BroadcastChannelService.h"
|
||||
#include "BroadcastChannelParent.h"
|
||||
#include "mozilla/ipc/BackgroundParent.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#undef PostMessage
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
using namespace ipc;
|
||||
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
|
||||
BroadcastChannelService* sInstance = nullptr;
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
BroadcastChannelService::BroadcastChannelService()
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
// sInstance is a raw BroadcastChannelService*.
|
||||
MOZ_ASSERT(!sInstance);
|
||||
sInstance = this;
|
||||
}
|
||||
|
||||
BroadcastChannelService::~BroadcastChannelService()
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(sInstance == this);
|
||||
MOZ_ASSERT(mAgents.Count() == 0);
|
||||
|
||||
sInstance = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<BroadcastChannelService>
|
||||
BroadcastChannelService::GetOrCreate()
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
nsRefPtr<BroadcastChannelService> instance = sInstance;
|
||||
if (!instance) {
|
||||
instance = new BroadcastChannelService();
|
||||
}
|
||||
return instance.forget();
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannelService::RegisterActor(BroadcastChannelParent* aParent)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(aParent);
|
||||
MOZ_ASSERT(!mAgents.Contains(aParent));
|
||||
|
||||
mAgents.PutEntry(aParent);
|
||||
}
|
||||
|
||||
void
|
||||
BroadcastChannelService::UnregisterActor(BroadcastChannelParent* aParent)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(aParent);
|
||||
MOZ_ASSERT(mAgents.Contains(aParent));
|
||||
|
||||
mAgents.RemoveEntry(aParent);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct MOZ_STACK_CLASS PostMessageData MOZ_FINAL
|
||||
{
|
||||
PostMessageData(BroadcastChannelParent* aParent,
|
||||
const ClonedMessageData& aData,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aChannel)
|
||||
: mParent(aParent)
|
||||
, mData(aData)
|
||||
, mOrigin(aOrigin)
|
||||
, mChannel(aChannel)
|
||||
{
|
||||
MOZ_ASSERT(aParent);
|
||||
MOZ_COUNT_CTOR(PostMessageData);
|
||||
}
|
||||
|
||||
~PostMessageData()
|
||||
{
|
||||
MOZ_COUNT_DTOR(PostMessageData);
|
||||
}
|
||||
|
||||
BroadcastChannelParent* mParent;
|
||||
const ClonedMessageData& mData;
|
||||
const nsString mOrigin;
|
||||
const nsString mChannel;
|
||||
};
|
||||
|
||||
PLDHashOperator
|
||||
PostMessageEnumerator(nsPtrHashKey<BroadcastChannelParent>* aKey, void* aPtr)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
|
||||
auto* data = static_cast<PostMessageData*>(aPtr);
|
||||
BroadcastChannelParent* parent = aKey->GetKey();
|
||||
MOZ_ASSERT(parent);
|
||||
|
||||
if (parent != data->mParent) {
|
||||
parent->CheckAndDeliver(data->mData, data->mOrigin, data->mChannel);
|
||||
}
|
||||
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
void
|
||||
BroadcastChannelService::PostMessage(BroadcastChannelParent* aParent,
|
||||
const ClonedMessageData& aData,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aChannel)
|
||||
{
|
||||
AssertIsOnBackgroundThread();
|
||||
MOZ_ASSERT(aParent);
|
||||
MOZ_ASSERT(mAgents.Contains(aParent));
|
||||
|
||||
PostMessageData data(aParent, aData, aOrigin, aChannel);
|
||||
mAgents.EnumerateEntries(PostMessageEnumerator, &data);
|
||||
}
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
47
dom/broadcastchannel/BroadcastChannelService.h
Normal file
47
dom/broadcastchannel/BroadcastChannelService.h
Normal file
@ -0,0 +1,47 @@
|
||||
/* 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_BroadcastChannelService_h
|
||||
#define mozilla_dom_BroadcastChannelService_h
|
||||
|
||||
#include "nsISupportsImpl.h"
|
||||
#include "nsHashKeys.h"
|
||||
#include "nsTHashtable.h"
|
||||
|
||||
#ifdef XP_WIN
|
||||
#undef PostMessage
|
||||
#endif
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
class BroadcastChannelParent;
|
||||
class ClonedMessageData;
|
||||
|
||||
class BroadcastChannelService MOZ_FINAL
|
||||
{
|
||||
public:
|
||||
NS_INLINE_DECL_REFCOUNTING(BroadcastChannelService)
|
||||
|
||||
static already_AddRefed<BroadcastChannelService> GetOrCreate();
|
||||
|
||||
void RegisterActor(BroadcastChannelParent* aParent);
|
||||
void UnregisterActor(BroadcastChannelParent* aParent);
|
||||
|
||||
void PostMessage(BroadcastChannelParent* aParent,
|
||||
const ClonedMessageData& aData,
|
||||
const nsAString& aOrigin,
|
||||
const nsAString& aChannel);
|
||||
|
||||
private:
|
||||
BroadcastChannelService();
|
||||
~BroadcastChannelService();
|
||||
|
||||
nsTHashtable<nsPtrHashKey<BroadcastChannelParent>> mAgents;
|
||||
};
|
||||
|
||||
} // dom namespace
|
||||
} // mozilla namespace
|
||||
|
||||
#endif // mozilla_dom_BroadcastChannelService_h
|
29
dom/broadcastchannel/PBroadcastChannel.ipdl
Normal file
29
dom/broadcastchannel/PBroadcastChannel.ipdl
Normal file
@ -0,0 +1,29 @@
|
||||
/* 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 protocol PBackground;
|
||||
include protocol PBlob;
|
||||
include DOMTypes;
|
||||
|
||||
using struct mozilla::SerializedStructuredCloneBuffer from "ipc/IPCMessageUtils.h";
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
// This protocol is used for the BroadcastChannel API
|
||||
protocol PBroadcastChannel
|
||||
{
|
||||
manager PBackground;
|
||||
|
||||
parent:
|
||||
PostMessage(ClonedMessageData message);
|
||||
Close();
|
||||
|
||||
child:
|
||||
Notify(ClonedMessageData message);
|
||||
__delete__();
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
31
dom/broadcastchannel/moz.build
Normal file
31
dom/broadcastchannel/moz.build
Normal file
@ -0,0 +1,31 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXPORTS.mozilla.dom += [
|
||||
'BroadcastChannel.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'BroadcastChannel.cpp',
|
||||
'BroadcastChannelChild.cpp',
|
||||
'BroadcastChannelParent.cpp',
|
||||
'BroadcastChannelService.cpp',
|
||||
]
|
||||
|
||||
IPDL_SOURCES += [
|
||||
'PBroadcastChannel.ipdl',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'../workers',
|
||||
]
|
||||
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
|
||||
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
FAIL_ON_WARNINGS = True
|
11
dom/broadcastchannel/tests/broadcastchannel_pref_worker.js
Normal file
11
dom/broadcastchannel/tests/broadcastchannel_pref_worker.js
Normal file
@ -0,0 +1,11 @@
|
||||
onmessage = function() {
|
||||
var exists = true;
|
||||
try {
|
||||
var bc = new BroadcastChannel('foobar');
|
||||
} catch(e) {
|
||||
exists = false;
|
||||
}
|
||||
|
||||
postMessage({ exists: exists });
|
||||
}
|
||||
|
12
dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js
Normal file
12
dom/broadcastchannel/tests/broadcastchannel_sharedWorker.js
Normal file
@ -0,0 +1,12 @@
|
||||
onconnect = function(evt) {
|
||||
evt.ports[0].onmessage = function(evt) {
|
||||
var bc = new BroadcastChannel('foobar');
|
||||
bc.addEventListener('message', function(event) {
|
||||
evt.target.postMessage(event.data == "hello world from the window" ? "OK" : "KO");
|
||||
bc.postMessage("hello world from the worker");
|
||||
bc.close();
|
||||
}, false);
|
||||
|
||||
evt.target.postMessage("READY");
|
||||
}
|
||||
}
|
18
dom/broadcastchannel/tests/broadcastchannel_worker.js
Normal file
18
dom/broadcastchannel/tests/broadcastchannel_worker.js
Normal file
@ -0,0 +1,18 @@
|
||||
onmessage = function(evt) {
|
||||
if (evt.data != 0) {
|
||||
var worker = new Worker("broadcastchannel_worker.js");
|
||||
worker.onmessage = function(evt) {
|
||||
postMessage(evt.data);
|
||||
}
|
||||
worker.postMessage(evt.data - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
var bc = new BroadcastChannel('foobar');
|
||||
bc.addEventListener('message', function(event) {
|
||||
bc.postMessage(event.data == "hello world from the window" ? "hello world from the worker" : "KO");
|
||||
bc.close();
|
||||
}, false);
|
||||
|
||||
postMessage("READY");
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
(new BroadcastChannel('foobar')).postMessage('READY');
|
||||
|
||||
(new BroadcastChannel('foobar')).addEventListener('message', function(event) {
|
||||
if (event.data != 'READY') {
|
||||
event.target.postMessage(event.data);
|
||||
}
|
||||
}, false);
|
||||
|
@ -0,0 +1,5 @@
|
||||
(new BroadcastChannel('foobar')).onmessage = function(event) {
|
||||
event.target.postMessage(event.data);
|
||||
}
|
||||
|
||||
postMessage("READY");
|
35
dom/broadcastchannel/tests/iframe_broadcastchannel.html
Normal file
35
dom/broadcastchannel/tests/iframe_broadcastchannel.html
Normal file
@ -0,0 +1,35 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<script type="application/javascript">
|
||||
|
||||
function is(a, b, msg) {
|
||||
ok(a == b, msg);
|
||||
}
|
||||
|
||||
function ok(a, msg) {
|
||||
window.parent.postMessage({ status: a ? "OK" : "KO", message: msg }, "*");
|
||||
}
|
||||
|
||||
ok("BroadcastChannel" in window, "BroadcastChannel exists");
|
||||
|
||||
var bc = new BroadcastChannel("foobar");
|
||||
ok(bc, "BroadcastChannel can be created");
|
||||
is(bc.name, 'foobar', "BroadcastChannel.name is foobar");
|
||||
|
||||
ok("postMessage" in bc, "BroadcastChannel has postMessage() method");
|
||||
|
||||
bc.onmessage = function(evt) {
|
||||
ok(evt instanceof MessageEvent, 'evt is a MessageEvent');
|
||||
is(evt.target, bc, 'MessageEvent.target is bc');
|
||||
is(evt.target.name, 'foobar', 'MessageEvent.target.name is foobar');
|
||||
is(evt.target.name, bc.name, 'MessageEvent.target.name is bc.name');
|
||||
is(evt.data, "Hello world from the window!", "Message received from the window");
|
||||
bc.postMessage("Hello world from the iframe!");
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
17
dom/broadcastchannel/tests/mochitest.ini
Normal file
17
dom/broadcastchannel/tests/mochitest.ini
Normal file
@ -0,0 +1,17 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
iframe_broadcastchannel.html
|
||||
broadcastchannel_pref_worker.js
|
||||
broadcastchannel_sharedWorker.js
|
||||
broadcastchannel_worker.js
|
||||
broadcastchannel_worker_alive.js
|
||||
broadcastchannel_worker_any.js
|
||||
|
||||
[test_broadcastchannel_any.html]
|
||||
[test_broadcastchannel_basic.html]
|
||||
[test_broadcastchannel_close.html]
|
||||
[test_broadcastchannel_self.html]
|
||||
[test_broadcastchannel_pref.html]
|
||||
[test_broadcastchannel_sharedWorker.html]
|
||||
[test_broadcastchannel_worker.html]
|
||||
[test_broadcastchannel_worker_alive.html]
|
110
dom/broadcastchannel/tests/test_broadcastchannel_any.html
Normal file
110
dom/broadcastchannel/tests/test_broadcastchannel_any.html
Normal file
@ -0,0 +1,110 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for BroadcastChannel</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
var tests = [
|
||||
'hello world',
|
||||
123,
|
||||
null,
|
||||
true,
|
||||
new Date(),
|
||||
[ 1, 'test', true, new Date() ],
|
||||
{ a: true, b: null, c: new Date(), d: [ true, false, {} ] },
|
||||
new Blob([123], { type: 'plain/text' })
|
||||
];
|
||||
|
||||
var currentTest = null;
|
||||
|
||||
function getType(a) {
|
||||
if (a === null || a === undefined)
|
||||
return 'null';
|
||||
|
||||
if (Array.isArray(a))
|
||||
return 'array';
|
||||
|
||||
if (typeof a == 'object')
|
||||
return 'object';
|
||||
|
||||
return 'primitive';
|
||||
}
|
||||
|
||||
function compare(a, b) {
|
||||
is (getType(a), getType(b), 'Type matches');
|
||||
|
||||
var type = getType(a);
|
||||
if (type == 'array') {
|
||||
is (a.length, b.length, 'Array.length matches');
|
||||
for (var i = 0; i < a.length; ++i) {
|
||||
compare(a[i], b[i]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == 'object') {
|
||||
ok (a !== b, 'They should not match');
|
||||
|
||||
var aProps = [];
|
||||
for (var p in a) aProps.push(p);
|
||||
|
||||
var bProps = [];
|
||||
for (var p in b) bProps.push(p);
|
||||
|
||||
is (aProps.length, bProps.length, 'Props match');
|
||||
is (aProps.sort().toSource(), bProps.sort().toSource(), 'Props match - using toSource()');
|
||||
|
||||
for (var p in a) {
|
||||
compare(a[p], b[p]);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != 'null') {
|
||||
is (a.toSource(), b.toSource(), 'Matching using toSource()');
|
||||
}
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
var bc = new BroadcastChannel("foobar");
|
||||
ok(bc, "BroadcastChannel can be created");
|
||||
|
||||
bc.onmessage = function(event) {
|
||||
compare(event.data, currentTest);
|
||||
next();
|
||||
}
|
||||
|
||||
function next() {
|
||||
if (!tests.length) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
currentTest = tests.shift();
|
||||
bc.postMessage(currentTest);
|
||||
}
|
||||
|
||||
var worker = new Worker("broadcastchannel_worker_any.js");
|
||||
worker.onmessage = function(event) {
|
||||
if (event.data == "READY") {
|
||||
next();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
63
dom/broadcastchannel/tests/test_broadcastchannel_basic.html
Normal file
63
dom/broadcastchannel/tests/test_broadcastchannel_basic.html
Normal file
@ -0,0 +1,63 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for BroadcastChannel</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function runTest() {
|
||||
addEventListener('message', receiveMessage, false);
|
||||
function receiveMessage(evt) {
|
||||
if (evt.data.status == 'OK') {
|
||||
ok(true, evt.data.message);
|
||||
} else if (evt.data.status == 'KO') {
|
||||
ok(false, evt.data.message);
|
||||
} else {
|
||||
ok(false, "Unknown message");
|
||||
}
|
||||
}
|
||||
|
||||
ok("BroadcastChannel" in window, "BroadcastChannel exists");
|
||||
|
||||
var bc = new BroadcastChannel("foobar");
|
||||
ok(bc, "BroadcastChannel can be created");
|
||||
is(bc.name, 'foobar', "BroadcastChannel.name is foobar");
|
||||
|
||||
ok("postMessage" in bc, "BroadcastChannel has postMessage() method");
|
||||
|
||||
bc.onmessage = function(evt) {
|
||||
ok(evt instanceof MessageEvent, "This is a MessageEvent");
|
||||
is(evt.target, bc, "MessageEvent.target is bc");
|
||||
is(evt.target.name, 'foobar', "MessageEvent.target.name is foobar");
|
||||
is(evt.target.name, bc.name, "MessageEvent.target.name == bc.name");
|
||||
ok(evt.origin.indexOf('http://mochi.test:8888') == 0, "MessageEvent.origin is correct");
|
||||
is(evt.data, "Hello world from the iframe!", "The message from the iframe has been received!");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
var div = document.getElementById("content");
|
||||
ok(div, "Parent exists");
|
||||
|
||||
var ifr = document.createElement("iframe");
|
||||
ifr.addEventListener("load", iframeLoaded, false);
|
||||
ifr.setAttribute('src', "iframe_broadcastchannel.html");
|
||||
div.appendChild(ifr);
|
||||
|
||||
function iframeLoaded() {
|
||||
bc.postMessage("Hello world from the window!");
|
||||
}
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
61
dom/broadcastchannel/tests/test_broadcastchannel_close.html
Normal file
61
dom/broadcastchannel/tests/test_broadcastchannel_close.html
Normal file
@ -0,0 +1,61 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for BroadcastChannel</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function runTest() {
|
||||
var receiver = new BroadcastChannel('foo');
|
||||
var sequence = [ '2', 'done' ];
|
||||
receiver.onmessage = function(e) {
|
||||
if (!sequence.length) {
|
||||
ok (false, 'No more data is expected');
|
||||
return;
|
||||
}
|
||||
|
||||
var data = sequence.shift();
|
||||
is(e.data, data);
|
||||
|
||||
if (!sequence.length) {
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var x = new BroadcastChannel('foo');
|
||||
x.close();
|
||||
try {
|
||||
x.postMessage('1');
|
||||
ok(false, "PostMessage should throw if called after a close().");
|
||||
} catch(e) {
|
||||
ok(true, "PostMessage should throw if called after a close().");
|
||||
}
|
||||
|
||||
var y = new BroadcastChannel('foo');
|
||||
y.postMessage('2');
|
||||
y.close();
|
||||
try {
|
||||
y.postMessage('3');
|
||||
ok(false, "PostMessage should throw if called after a close().");
|
||||
} catch(e) {
|
||||
ok(true, "PostMessage should throw if called after a close().");
|
||||
}
|
||||
|
||||
var z = new BroadcastChannel('foo');
|
||||
z.postMessage('done');
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
72
dom/broadcastchannel/tests/test_broadcastchannel_pref.html
Normal file
72
dom/broadcastchannel/tests/test_broadcastchannel_pref.html
Normal file
@ -0,0 +1,72 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for BroadcastChannel</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function testNoPref() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", false]]}, function() {
|
||||
ok(!("BroadcastChannel" in window), "BroadcastChannel should not exist");
|
||||
runTests();
|
||||
});
|
||||
}
|
||||
|
||||
function testPref() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, function() {
|
||||
ok("BroadcastChannel" in window, "BroadcastChannel should exist");
|
||||
runTests();
|
||||
});
|
||||
}
|
||||
|
||||
function testNoPrefWorker() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", false]]}, function() {
|
||||
var worker = new Worker("broadcastchannel_pref_worker.js");
|
||||
worker.onmessage = function(event) {
|
||||
ok(!event.data.exists, "BroadcastChannel should not exist in workers");
|
||||
runTests();
|
||||
}
|
||||
worker.postMessage('go!');
|
||||
});
|
||||
}
|
||||
|
||||
function testPrefWorker() {
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, function() {
|
||||
var worker = new Worker("broadcastchannel_pref_worker.js");
|
||||
worker.onmessage = function(event) {
|
||||
ok(event.data.exists, "BroadcastChannel should exist in workers");
|
||||
runTests();
|
||||
}
|
||||
worker.postMessage('go!');
|
||||
});
|
||||
}
|
||||
|
||||
var tests = [
|
||||
testNoPref,
|
||||
testPref,
|
||||
testNoPrefWorker,
|
||||
testPrefWorker
|
||||
];
|
||||
|
||||
function runTests() {
|
||||
if (tests.length == 0) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var test = tests.shift();
|
||||
test();
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
runTests();
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
38
dom/broadcastchannel/tests/test_broadcastchannel_self.html
Normal file
38
dom/broadcastchannel/tests/test_broadcastchannel_self.html
Normal file
@ -0,0 +1,38 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for BroadcastChannel</title>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="content"></div>
|
||||
|
||||
<script type="application/javascript">
|
||||
|
||||
function runTest() {
|
||||
x = new BroadcastChannel('foo');
|
||||
y = new BroadcastChannel('foo');
|
||||
|
||||
function func(e) {
|
||||
is(e.target, y, "The target is !x");
|
||||
|
||||
SimpleTest.executeSoon(function() {
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}
|
||||
|
||||
x.onmessage = func;
|
||||
y.onmessage = func;
|
||||
|
||||
x.postMessage('foo');
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTest);
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -0,0 +1,55 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Tests of DOM BroadcastChannel in SharedWorkers
|
||||
-->
|
||||
<head>
|
||||
<title>Test for BroadcastChannel in SharedWorkers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" language="javascript">
|
||||
|
||||
function runTests() {
|
||||
var worker = new SharedWorker("broadcastchannel_sharedWorker.js");
|
||||
|
||||
var bc = new BroadcastChannel('foobar');
|
||||
|
||||
worker.port.onmessage = function(event) {
|
||||
if (event.data == "READY") {
|
||||
ok(true, "SharedWorker is ready!");
|
||||
bc.postMessage('hello world from the window');
|
||||
} else if(event.data == "OK") {
|
||||
ok(true, "SharedWorker has received the message");
|
||||
} else {
|
||||
ok(false, "Something wrong happened");
|
||||
}
|
||||
};
|
||||
|
||||
bc.onmessage = function(event) {
|
||||
is("hello world from the worker", event.data, "The message matches!");
|
||||
bc.close();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
worker.port.postMessage('go');
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true],
|
||||
["dom.workers.sharedWorkers.enabled", true]]}, runTests);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
62
dom/broadcastchannel/tests/test_broadcastchannel_worker.html
Normal file
62
dom/broadcastchannel/tests/test_broadcastchannel_worker.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Tests of DOM BroadcastChannel in workers
|
||||
-->
|
||||
<head>
|
||||
<title>Test for BroadcastChannel in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" language="javascript">
|
||||
|
||||
function testWorker(x) {
|
||||
var worker = new Worker("broadcastchannel_worker.js");
|
||||
|
||||
var bc = new BroadcastChannel('foobar');
|
||||
|
||||
worker.onmessage = function(event) {
|
||||
if (event.data == "READY") {
|
||||
ok(true, "Worker is ready!");
|
||||
bc.postMessage('hello world from the window');
|
||||
} else {
|
||||
ok(false, "Something wrong happened");
|
||||
}
|
||||
};
|
||||
|
||||
bc.onmessage = function(event) {
|
||||
is("hello world from the worker", event.data, "The message matches!");
|
||||
bc.close();
|
||||
runTests();
|
||||
}
|
||||
|
||||
worker.postMessage(x);
|
||||
}
|
||||
|
||||
var tests = [ 0, 3 ];
|
||||
function runTests() {
|
||||
if (tests.length == 0) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
testWorker(tests.shift());
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTests);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,56 @@
|
||||
<!--
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
-->
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Tests of DOM BroadcastChannel in workers
|
||||
-->
|
||||
<head>
|
||||
<title>Test for BroadcastChannel in workers</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
<script class="testbody" language="javascript">
|
||||
|
||||
function runTests() {
|
||||
var id = 0;
|
||||
(new BroadcastChannel('foobar')).onmessage = function(event) {
|
||||
info("MSG: " + event.data);
|
||||
|
||||
if (event.data == "READY") {
|
||||
ok(true, "Worker is ready!");
|
||||
} else {
|
||||
is(id, event.data, "The message is correct: " + id);
|
||||
}
|
||||
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
SpecialPowers.forceCC();
|
||||
SpecialPowers.forceGC();
|
||||
}
|
||||
|
||||
if (id == 5) {
|
||||
SimpleTest.finish();
|
||||
return;
|
||||
}
|
||||
|
||||
event.target.postMessage(++id);
|
||||
};
|
||||
|
||||
new Worker("broadcastchannel_worker_alive.js");
|
||||
}
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
SpecialPowers.pushPrefEnv({"set": [["dom.broadcastChannel.enabled", true]]}, runTests);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -101,6 +101,11 @@ public:
|
||||
return static_cast<DOMEventTargetHelper*>(target);
|
||||
}
|
||||
|
||||
bool HasListenersFor(const nsAString& aType)
|
||||
{
|
||||
return mListenerManager && mListenerManager->HasListenersFor(aType);
|
||||
}
|
||||
|
||||
bool HasListenersFor(nsIAtom* aTypeWithOn)
|
||||
{
|
||||
return mListenerManager && mListenerManager->HasListenersFor(aTypeWithOn);
|
||||
|
@ -101,8 +101,6 @@ EventListenerManager::EventListenerManager(EventTarget* aTarget)
|
||||
, mMayHaveScrollWheelEventListener(false)
|
||||
, mMayHaveMouseEnterLeaveEventListener(false)
|
||||
, mMayHavePointerEnterLeaveEventListener(false)
|
||||
, mMayHaveKeyEventListener(false)
|
||||
, mMayHaveInputOrCompositionEventListener(false)
|
||||
, mClearingListeners(false)
|
||||
, mIsMainThreadELM(NS_IsMainThread())
|
||||
, mNoListenerForEvent(0)
|
||||
@ -388,21 +386,7 @@ EventListenerManager::AddEventListenerInternal(
|
||||
window->SetHasGamepadEventListener();
|
||||
}
|
||||
#endif
|
||||
} else if (aTypeAtom == nsGkAtoms::onkeydown ||
|
||||
aTypeAtom == nsGkAtoms::onkeypress ||
|
||||
aTypeAtom == nsGkAtoms::onkeyup) {
|
||||
if (!aFlags.mInSystemGroup) {
|
||||
mMayHaveKeyEventListener = true;
|
||||
}
|
||||
} else if (aTypeAtom == nsGkAtoms::oncompositionend ||
|
||||
aTypeAtom == nsGkAtoms::oncompositionstart ||
|
||||
aTypeAtom == nsGkAtoms::oncompositionupdate ||
|
||||
aTypeAtom == nsGkAtoms::oninput) {
|
||||
if (!aFlags.mInSystemGroup) {
|
||||
mMayHaveInputOrCompositionEventListener = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (aTypeAtom && mTarget) {
|
||||
mTarget->EventListenerAdded(aTypeAtom);
|
||||
}
|
||||
@ -1263,8 +1247,19 @@ EventListenerManager::MutationListenerBits()
|
||||
bool
|
||||
EventListenerManager::HasListenersFor(const nsAString& aEventName)
|
||||
{
|
||||
nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aEventName);
|
||||
return HasListenersFor(atom);
|
||||
if (mIsMainThreadELM) {
|
||||
nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aEventName);
|
||||
return HasListenersFor(atom);
|
||||
}
|
||||
|
||||
uint32_t count = mListeners.Length();
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
Listener* listener = &mListeners.ElementAt(i);
|
||||
if (listener->mTypeString == aEventName) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
|
@ -403,19 +403,6 @@ public:
|
||||
bool MayHaveMouseEnterLeaveEventListener() { return mMayHaveMouseEnterLeaveEventListener; }
|
||||
bool MayHavePointerEnterLeaveEventListener() { return mMayHavePointerEnterLeaveEventListener; }
|
||||
|
||||
/**
|
||||
* Returns true if there may be a key event listener (keydown, keypress,
|
||||
* or keyup) registered, or false if there definitely isn't.
|
||||
*/
|
||||
bool MayHaveKeyEventListener() { return mMayHaveKeyEventListener; }
|
||||
|
||||
/**
|
||||
* Returns true if there may be an advanced input event listener (input,
|
||||
* compositionstart, compositionupdate, or compositionend) registered,
|
||||
* or false if there definitely isn't.
|
||||
*/
|
||||
bool MayHaveInputOrCompositionEventListener() { return mMayHaveInputOrCompositionEventListener; }
|
||||
|
||||
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
uint32_t ListenerCount() const
|
||||
@ -566,11 +553,9 @@ protected:
|
||||
uint32_t mMayHaveScrollWheelEventListener : 1;
|
||||
uint32_t mMayHaveMouseEnterLeaveEventListener : 1;
|
||||
uint32_t mMayHavePointerEnterLeaveEventListener : 1;
|
||||
uint32_t mMayHaveKeyEventListener : 1;
|
||||
uint32_t mMayHaveInputOrCompositionEventListener : 1;
|
||||
uint32_t mClearingListeners : 1;
|
||||
uint32_t mIsMainThreadELM : 1;
|
||||
uint32_t mNoListenerForEvent : 20;
|
||||
uint32_t mNoListenerForEvent : 23;
|
||||
|
||||
nsAutoTObserverArray<Listener, 2> mListeners;
|
||||
dom::EventTarget* mTarget; // WEAK
|
||||
|
@ -9,7 +9,6 @@
|
||||
#include "mozilla/IMEStateManager.h"
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "mozilla/EventListenerManager.h"
|
||||
#include "mozilla/EventStates.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
@ -17,6 +16,7 @@
|
||||
#include "mozilla/TextComposition.h"
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "mozilla/dom/HTMLFormElement.h"
|
||||
#include "mozilla/dom/TabParent.h"
|
||||
|
||||
#include "HTMLInputElement.h"
|
||||
#include "IMEContentObserver.h"
|
||||
@ -182,7 +182,6 @@ nsPresContext* IMEStateManager::sPresContext = nullptr;
|
||||
bool IMEStateManager::sInstalledMenuKeyboardListener = false;
|
||||
bool IMEStateManager::sIsTestingIME = false;
|
||||
bool IMEStateManager::sIsGettingNewIMEState = false;
|
||||
bool IMEStateManager::sCheckForIMEUnawareWebApps = false;
|
||||
|
||||
// sActiveIMEContentObserver points to the currently active IMEContentObserver.
|
||||
// sActiveIMEContentObserver is null if there is no focused editor.
|
||||
@ -198,10 +197,6 @@ IMEStateManager::Init()
|
||||
sISMLog = PR_NewLogModule("IMEStateManager");
|
||||
}
|
||||
#endif
|
||||
Preferences::AddBoolVarCache(
|
||||
&sCheckForIMEUnawareWebApps,
|
||||
"intl.ime.hack.on_ime_unaware_apps.fire_key_events_for_composition",
|
||||
false);
|
||||
}
|
||||
|
||||
// static
|
||||
@ -394,6 +389,27 @@ IMEStateManager::OnChangeFocusInternal(nsPresContext* aPresContext,
|
||||
}
|
||||
|
||||
IMEState newState = GetNewIMEState(aPresContext, aContent);
|
||||
|
||||
// In e10s, remote content may have IME focus. The main process (i.e. this process)
|
||||
// would attempt to set state to DISABLED if, for example, the user clicks
|
||||
// some other remote content. The content process would later re-ENABLE IME, meaning
|
||||
// that all state-changes were unnecessary.
|
||||
// Here we filter the common case where the main process knows that the remote
|
||||
// process controls IME focus. The DISABLED->re-ENABLED progression can
|
||||
// still happen since remote content may be concurrently communicating its claim
|
||||
// on focus to the main process... but this cannot cause bugs like missed keypresses.
|
||||
// (It just means a lot of needless IPC.)
|
||||
if ((newState.mEnabled == IMEState::DISABLED) && TabParent::GetIMETabParent()) {
|
||||
PR_LOG(sISMLog, PR_LOG_DEBUG,
|
||||
("ISM: IMEStateManager::OnChangeFocusInternal(), "
|
||||
"Parent process cancels to set DISABLED state because the content process "
|
||||
"has IME focus and has already sets IME state"));
|
||||
MOZ_ASSERT(XRE_IsParentProcess(),
|
||||
"TabParent::GetIMETabParent() should never return non-null value "
|
||||
"in the content process");
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
if (!focusActuallyChanging) {
|
||||
// actual focus isn't changing, but if IME enabled state is changing,
|
||||
// we should do it.
|
||||
@ -753,25 +769,6 @@ private:
|
||||
uint32_t mState;
|
||||
};
|
||||
|
||||
static bool
|
||||
MayBeIMEUnawareWebApp(nsINode* aNode)
|
||||
{
|
||||
bool haveKeyEventsListener = false;
|
||||
|
||||
while (aNode) {
|
||||
EventListenerManager* const mgr = aNode->GetExistingListenerManager();
|
||||
if (mgr) {
|
||||
if (mgr->MayHaveInputOrCompositionEventListener()) {
|
||||
return false;
|
||||
}
|
||||
haveKeyEventsListener |= mgr->MayHaveKeyEventListener();
|
||||
}
|
||||
aNode = aNode->GetParentNode();
|
||||
}
|
||||
|
||||
return haveKeyEventsListener;
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
IMEStateManager::SetIMEState(const IMEState& aState,
|
||||
@ -794,9 +791,6 @@ IMEStateManager::SetIMEState(const IMEState& aState,
|
||||
InputContext context;
|
||||
context.mIMEState = aState;
|
||||
|
||||
context.mMayBeIMEUnaware = context.mIMEState.IsEditable() &&
|
||||
sCheckForIMEUnawareWebApps && MayBeIMEUnawareWebApp(aContent);
|
||||
|
||||
if (aContent && aContent->GetNameSpaceID() == kNameSpaceID_XHTML &&
|
||||
(aContent->Tag() == nsGkAtoms::input ||
|
||||
aContent->Tag() == nsGkAtoms::textarea)) {
|
||||
|
@ -164,7 +164,6 @@ protected:
|
||||
static bool sInstalledMenuKeyboardListener;
|
||||
static bool sIsTestingIME;
|
||||
static bool sIsGettingNewIMEState;
|
||||
static bool sCheckForIMEUnawareWebApps;
|
||||
|
||||
class MOZ_STACK_CLASS GettingNewIMEStateBlocker MOZ_FINAL
|
||||
{
|
||||
|
@ -119,14 +119,23 @@ MessageEvent::Constructor(const GlobalObject& aGlobal,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
nsRefPtr<MessageEvent> event = new MessageEvent(t, nullptr, nullptr);
|
||||
return Constructor(t, aType, aParam, aRv);
|
||||
}
|
||||
|
||||
/* static */ already_AddRefed<MessageEvent>
|
||||
MessageEvent::Constructor(EventTarget* aEventTarget,
|
||||
const nsAString& aType,
|
||||
const MessageEventInit& aParam,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<MessageEvent> event = new MessageEvent(aEventTarget, nullptr, nullptr);
|
||||
|
||||
aRv = event->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
|
||||
if (aRv.Failed()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool trusted = event->Init(t);
|
||||
bool trusted = event->Init(aEventTarget);
|
||||
event->SetTrusted(trusted);
|
||||
|
||||
event->mData = aParam.mData;
|
||||
|
@ -74,6 +74,12 @@ public:
|
||||
const MessageEventInit& aEventInit,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<MessageEvent>
|
||||
Constructor(EventTarget* aEventTarget,
|
||||
const nsAString& aType,
|
||||
const MessageEventInit& aEventInit,
|
||||
ErrorResult& aRv);
|
||||
|
||||
protected:
|
||||
~MessageEvent();
|
||||
|
||||
|
@ -275,13 +275,13 @@ parent:
|
||||
int32_t IMEOpen,
|
||||
intptr_t NativeIMEContext);
|
||||
|
||||
prio(urgent) async SetInputContext(int32_t IMEEnabled,
|
||||
int32_t IMEOpen,
|
||||
nsString type,
|
||||
nsString inputmode,
|
||||
nsString actionHint,
|
||||
int32_t cause,
|
||||
int32_t focusChange);
|
||||
prio(urgent) sync SetInputContext(int32_t IMEEnabled,
|
||||
int32_t IMEOpen,
|
||||
nsString type,
|
||||
nsString inputmode,
|
||||
nsString actionHint,
|
||||
int32_t cause,
|
||||
int32_t focusChange);
|
||||
|
||||
sync IsParentWindowMainWidgetVisible() returns (bool visible);
|
||||
|
||||
|
@ -26,15 +26,17 @@ namespace {
|
||||
void
|
||||
Error(JSContext* aCx, uint32_t aErrorId)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
NS_DOMStructuredCloneError(aCx, aErrorId);
|
||||
if (NS_IsMainThread()) {
|
||||
NS_DOMStructuredCloneError(aCx, aErrorId);
|
||||
} else {
|
||||
Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
|
||||
}
|
||||
}
|
||||
|
||||
JSObject*
|
||||
Read(JSContext* aCx, JSStructuredCloneReader* aReader, uint32_t aTag,
|
||||
uint32_t aData, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aClosure);
|
||||
|
||||
StructuredCloneClosure* closure =
|
||||
@ -80,7 +82,6 @@ bool
|
||||
Write(JSContext* aCx, JSStructuredCloneWriter* aWriter,
|
||||
JS::Handle<JSObject*> aObj, void* aClosure)
|
||||
{
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
MOZ_ASSERT(aClosure);
|
||||
|
||||
StructuredCloneClosure* closure =
|
||||
|
@ -1925,14 +1925,25 @@ TabParent::RecvSetInputContext(const int32_t& aIMEEnabled,
|
||||
const int32_t& aCause,
|
||||
const int32_t& aFocusChange)
|
||||
{
|
||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||
if (!widget || !AllowContentIME()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
InputContext oldContext = widget->GetInputContext();
|
||||
|
||||
// Ignore if current widget IME setting is not DISABLED and didn't come
|
||||
// from remote content. Chrome content may have taken over.
|
||||
if (oldContext.mIMEState.mEnabled != IMEState::DISABLED &&
|
||||
oldContext.IsOriginMainProcess()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// mIMETabParent (which is actually static) tracks which if any TabParent has IMEFocus
|
||||
// When the input mode is set to anything but IMEState::DISABLED,
|
||||
// mIMETabParent should be set to this
|
||||
mIMETabParent =
|
||||
aIMEEnabled != static_cast<int32_t>(IMEState::DISABLED) ? this : nullptr;
|
||||
nsCOMPtr<nsIWidget> widget = GetWidget();
|
||||
if (!widget || !AllowContentIME())
|
||||
return true;
|
||||
|
||||
InputContext context;
|
||||
context.mIMEState.mEnabled = static_cast<IMEState::Enabled>(aIMEEnabled);
|
||||
@ -1940,6 +1951,8 @@ TabParent::RecvSetInputContext(const int32_t& aIMEEnabled,
|
||||
context.mHTMLInputType.Assign(aType);
|
||||
context.mHTMLInputInputmode.Assign(aInputmode);
|
||||
context.mActionHint.Assign(aActionHint);
|
||||
context.mOrigin = InputContext::ORIGIN_CONTENT;
|
||||
|
||||
InputContextAction action(
|
||||
static_cast<InputContextAction::Cause>(aCause),
|
||||
static_cast<InputContextAction::FocusChange>(aFocusChange));
|
||||
@ -2195,7 +2208,11 @@ TabParent::MaybeForwardEventToRenderFrame(WidgetInputEvent& aEvent,
|
||||
ScrollableLayerGuid* aOutTargetGuid,
|
||||
uint64_t* aOutInputBlockId)
|
||||
{
|
||||
if (aEvent.mClass == eWheelEventClass) {
|
||||
if (aEvent.mClass == eWheelEventClass
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
|| aEvent.mClass == eTouchEventClass
|
||||
#endif
|
||||
) {
|
||||
// Wheel events must be sent to APZ directly from the widget. New APZ-
|
||||
// aware events should follow suit and move there as well. However, we
|
||||
// do need to inform the child process of the correct target and block
|
||||
|
@ -92,6 +92,7 @@ InvokeAndRetry(ThisType* aThisVal, ReturnType(ThisType::*aMethod)(), MP4Stream*
|
||||
if (NS_WARN_IF(!stream->LastReadFailed(&failure))) {
|
||||
return result;
|
||||
}
|
||||
stream->ClearFailedRead();
|
||||
|
||||
if (NS_WARN_IF(failure == prevFailure)) {
|
||||
NS_WARNING(nsPrintfCString("Failed reading the same block twice: offset=%lld, count=%lu",
|
||||
|
@ -45,6 +45,8 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
void ClearFailedRead() { mFailedRead.reset(); }
|
||||
|
||||
void Pin()
|
||||
{
|
||||
mResource->Pin();
|
||||
|
@ -478,6 +478,26 @@ MediaSourceReader::SwitchVideoReader(int64_t aTarget, int64_t aError)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
MediaSourceReader::IsDormantNeeded()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
if (mVideoReader) {
|
||||
return mVideoReader->IsDormantNeeded();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
MediaSourceReader::ReleaseMediaResources()
|
||||
{
|
||||
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
|
||||
if (mVideoReader) {
|
||||
mVideoReader->ReleaseMediaResources();
|
||||
}
|
||||
}
|
||||
|
||||
MediaDecoderReader*
|
||||
CreateReaderForType(const nsACString& aType, AbstractMediaDecoder* aDecoder)
|
||||
{
|
||||
|
@ -53,6 +53,9 @@ public:
|
||||
virtual size_t SizeOfVideoQueueInFrames() MOZ_OVERRIDE;
|
||||
virtual size_t SizeOfAudioQueueInFrames() MOZ_OVERRIDE;
|
||||
|
||||
virtual bool IsDormantNeeded() MOZ_OVERRIDE;
|
||||
virtual void ReleaseMediaResources() MOZ_OVERRIDE;
|
||||
|
||||
void OnAudioDecoded(AudioData* aSample);
|
||||
void OnAudioNotDecoded(NotDecodedReason aReason);
|
||||
void OnVideoDecoded(VideoData* aSample);
|
||||
|
@ -29,7 +29,7 @@ extern PRLogModuleInfo* GetMediaSourceAPILog();
|
||||
#define MSE_DEBUG(...)
|
||||
#endif
|
||||
|
||||
#define UNIMPLEMENTED() MSE_DEBUG("SourceBufferResource(%p): UNIMPLEMENTED FUNCTION at %s:%d", this, __FILE__, __LINE__)
|
||||
#define UNIMPLEMENTED() { /* Logging this is too spammy to do by default */ }
|
||||
|
||||
class nsIStreamListener;
|
||||
|
||||
|
@ -51,7 +51,7 @@ function createTestArray() {
|
||||
}
|
||||
|
||||
function log(msg) {
|
||||
//dump(msg + "\n");
|
||||
info(msg);
|
||||
var l = document.getElementById('log');
|
||||
l.innerHTML += msg + "<br>";
|
||||
}
|
||||
@ -71,7 +71,15 @@ function finish(v) {
|
||||
|
||||
function listener(evt) {
|
||||
var v = evt.target;
|
||||
//log(filename(v.name) + ' got event ' + evt.type);
|
||||
log(filename(v.name) + ': got ' + evt.type);
|
||||
|
||||
// On slow machines like B2G emulator, progress timer could time out before
|
||||
// receiving any HTTP notification. We will ignore the 'stalled' event to
|
||||
// pass the tests.
|
||||
if (evt.type == 'stalled') {
|
||||
return;
|
||||
}
|
||||
|
||||
ok(v.eventNum < gExpectedEvents.length, filename(v.name) + " Too many events received");
|
||||
var expected = (v.eventNum < gExpectedEvents.length) ? gExpectedEvents[v.eventNum] : "NoEvent";
|
||||
is(evt.type, expected, filename(v.name) + " Events received in wrong order");
|
||||
|
@ -535,7 +535,7 @@ var commandsPeerConnection = [
|
||||
test.pcLocal.checkStatsIceConnections(stats,
|
||||
test._offer_constraints,
|
||||
test._offer_options,
|
||||
test.originalAnswer);
|
||||
test._remote_answer);
|
||||
test.next();
|
||||
});
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ DIRS += [
|
||||
'workers',
|
||||
'camera',
|
||||
'audiochannel',
|
||||
'broadcastchannel',
|
||||
'promise',
|
||||
'smil',
|
||||
'telephony',
|
||||
|
@ -61,7 +61,7 @@ TCPServerSocketParent::Init(PNeckoParent* neckoParent, const uint16_t& aLocalPor
|
||||
}
|
||||
|
||||
rv = mIntermediary->Listen(this, aLocalPort, aBacklog, aBinaryType, GetAppId(),
|
||||
getter_AddRefs(mServerSocket));
|
||||
GetInBrowser(), getter_AddRefs(mServerSocket));
|
||||
if (NS_FAILED(rv) || !mServerSocket) {
|
||||
FireInteralError(this, __LINE__);
|
||||
return true;
|
||||
@ -82,6 +82,19 @@ TCPServerSocketParent::GetAppId()
|
||||
return appId;
|
||||
};
|
||||
|
||||
bool
|
||||
TCPServerSocketParent::GetInBrowser()
|
||||
{
|
||||
bool inBrowser = false;
|
||||
const PContentParent *content = Manager()->Manager();
|
||||
const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
|
||||
if (browsers.Length() > 0) {
|
||||
TabParent *tab = static_cast<TabParent*>(browsers[0]);
|
||||
inBrowser = tab->IsBrowserElement();
|
||||
}
|
||||
return inBrowser;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
TCPServerSocketParent::SendCallbackAccept(nsITCPSocketParent *socket)
|
||||
{
|
||||
|
@ -32,6 +32,7 @@ public:
|
||||
virtual bool RecvRequestDelete() MOZ_OVERRIDE;
|
||||
|
||||
uint32_t GetAppId();
|
||||
bool GetInBrowser();
|
||||
|
||||
void AddIPDLReference();
|
||||
void ReleaseIPDLReference();
|
||||
|
@ -184,6 +184,7 @@ TCPSocket.prototype = {
|
||||
_txBytes: 0,
|
||||
_rxBytes: 0,
|
||||
_appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
|
||||
_inBrowser: false,
|
||||
_activeNetwork: null,
|
||||
#endif
|
||||
|
||||
@ -481,6 +482,14 @@ TCPSocket.prototype = {
|
||||
#endif
|
||||
},
|
||||
|
||||
setInBrowser: function ts_setInBrowser(inBrowser) {
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
this._inBrowser = inBrowser;
|
||||
#else
|
||||
// Do nothing.
|
||||
#endif
|
||||
},
|
||||
|
||||
setOnUpdateBufferedAmountHandler: function(aFunction) {
|
||||
if (typeof(aFunction) == 'function') {
|
||||
this._onUpdateBufferedAmount = aFunction;
|
||||
|
@ -93,6 +93,19 @@ TCPSocketParent::GetAppId()
|
||||
return appId;
|
||||
};
|
||||
|
||||
bool
|
||||
TCPSocketParent::GetInBrowser()
|
||||
{
|
||||
bool inBrowser = false;
|
||||
const PContentParent *content = Manager()->Manager();
|
||||
const InfallibleTArray<PBrowserParent*>& browsers = content->ManagedPBrowserParent();
|
||||
if (browsers.Length() > 0) {
|
||||
TabParent *tab = static_cast<TabParent*>(browsers[0]);
|
||||
inBrowser = tab->IsBrowserElement();
|
||||
}
|
||||
return inBrowser;
|
||||
}
|
||||
|
||||
nsresult
|
||||
TCPSocketParent::OfflineNotification(nsISupports *aSubject)
|
||||
{
|
||||
@ -162,6 +175,7 @@ TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bo
|
||||
|
||||
// Obtain App ID
|
||||
uint32_t appId = GetAppId();
|
||||
bool inBrowser = GetInBrowser();
|
||||
|
||||
if (NS_IsAppOffline(appId)) {
|
||||
NS_ERROR("Can't open socket because app is offline");
|
||||
@ -177,7 +191,7 @@ TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bo
|
||||
}
|
||||
|
||||
rv = mIntermediary->Open(this, aHost, aPort, aUseSSL, aBinaryType, appId,
|
||||
getter_AddRefs(mSocket));
|
||||
inBrowser, getter_AddRefs(mSocket));
|
||||
if (NS_FAILED(rv) || !mSocket) {
|
||||
FireInteralError(this, __LINE__);
|
||||
return true;
|
||||
|
@ -63,6 +63,7 @@ public:
|
||||
virtual bool RecvRequestDelete() MOZ_OVERRIDE;
|
||||
virtual nsresult OfflineNotification(nsISupports *) MOZ_OVERRIDE;
|
||||
virtual uint32_t GetAppId() MOZ_OVERRIDE;
|
||||
bool GetInBrowser();
|
||||
|
||||
private:
|
||||
virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
|
||||
|
@ -36,7 +36,8 @@ TCPSocketParentIntermediary.prototype = {
|
||||
aParentSide.sendUpdateBufferedAmount(aBufferedAmount, aTrackingNumber);
|
||||
},
|
||||
|
||||
open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType, aAppId) {
|
||||
open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType,
|
||||
aAppId, aInBrowser) {
|
||||
let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
|
||||
let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType});
|
||||
if (!socket)
|
||||
@ -44,6 +45,7 @@ TCPSocketParentIntermediary.prototype = {
|
||||
|
||||
let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
|
||||
socketInternal.setAppId(aAppId);
|
||||
socketInternal.setInBrowser(aInBrowser);
|
||||
|
||||
// Handle parent's request to update buffered amount.
|
||||
socketInternal.setOnUpdateBufferedAmountHandler(
|
||||
@ -54,7 +56,8 @@ TCPSocketParentIntermediary.prototype = {
|
||||
return socket;
|
||||
},
|
||||
|
||||
listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType, aAppId) {
|
||||
listen: function(aTCPServerSocketParent, aLocalPort, aBacklog, aBinaryType,
|
||||
aAppId, aInBrowser) {
|
||||
let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
|
||||
let serverSocket = baseSocket.listen(aLocalPort, { binaryType: aBinaryType }, aBacklog);
|
||||
if (!serverSocket)
|
||||
@ -69,6 +72,7 @@ TCPSocketParentIntermediary.prototype = {
|
||||
|
||||
let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal);
|
||||
socketInternal.setAppId(aAppId);
|
||||
socketInternal.setInBrowser(aInBrowser);
|
||||
socketInternal.setOnUpdateBufferedAmountHandler(
|
||||
intermediary._onUpdateBufferedAmountHandler.bind(intermediary, socketParent));
|
||||
|
||||
|
@ -216,7 +216,7 @@ interface nsIDOMTCPSocket : nsISupports
|
||||
* Needed to account for multiple possible types that can be provided to
|
||||
* the socket callbacks as arguments.
|
||||
*/
|
||||
[scriptable, uuid(b1235064-9a08-4714-ad03-1212e4562803)]
|
||||
[scriptable, uuid(ac2c4b69-cb79-4767-b1ce-bcf62945cd39)]
|
||||
interface nsITCPSocketInternal : nsISupports {
|
||||
// Trigger the callback for |type| and provide a DOMError() object with the given data
|
||||
void callListenerError(in DOMString type, in DOMString name);
|
||||
@ -276,6 +276,9 @@ interface nsITCPSocketInternal : nsISupports {
|
||||
// Set App ID.
|
||||
void setAppId(in unsigned long appId);
|
||||
|
||||
// Set inBrowser.
|
||||
void setInBrowser(in boolean inBrowser);
|
||||
|
||||
// Set a callback that handles the request from a TCP socket parent when that
|
||||
// socket parent wants to notify that its bufferedAmount is updated.
|
||||
void setOnUpdateBufferedAmountHandler(in jsval handler);
|
||||
|
@ -65,19 +65,21 @@ interface nsITCPSocketParent : nsISupports
|
||||
// This interface is the bridge of TCPSocketParent, which is written in C++,
|
||||
// and TCPSocket, which is written in Javascript. TCPSocketParentIntermediary
|
||||
// implements nsITCPSocketIntermediary in Javascript.
|
||||
[scriptable, uuid(0bc14635-c586-4046-b82f-27ff45e6c39c)]
|
||||
[scriptable, uuid(aa9bd46d-26bf-4ba8-9c18-ba02482c02f0)]
|
||||
interface nsITCPSocketIntermediary : nsISupports {
|
||||
// Open the connection to the server with the given parameters
|
||||
nsIDOMTCPSocket open(in nsITCPSocketParent parent,
|
||||
in DOMString host, in unsigned short port,
|
||||
in boolean useSSL, in DOMString binaryType,
|
||||
in unsigned long appId);
|
||||
in unsigned long appId,
|
||||
in boolean inBrowser);
|
||||
|
||||
// Listen on a port
|
||||
nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent,
|
||||
in unsigned short port, in unsigned short backlog,
|
||||
in DOMString binaryType,
|
||||
in unsigned long appId);
|
||||
in unsigned long appId,
|
||||
in boolean inBrowser);
|
||||
|
||||
// Called when received a child request to send a string.
|
||||
void onRecvSendString(in DOMString data, in uint32_t trackingNumber);
|
||||
|
@ -199,6 +199,8 @@ var interfaceNamesInGlobalScope =
|
||||
{name: "BluetoothStatusChangedEvent", b2g: true, permission: ["bluetooth"]},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "BoxObject", xbl: true},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "BroadcastChannel", pref: "dom.broadcastChannel.enabled"},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
{name: "CallEvent", b2g: true, pref: "dom.telephony.enabled"},
|
||||
// IMPORTANT: Do not change this list without review from a DOM peer!
|
||||
|
@ -25,4 +25,4 @@ support-files =
|
||||
iframe_differentDOM.html
|
||||
|
||||
[test_pointerlock-api.html]
|
||||
skip-if = (toolkit == "windows" && debug) || buildapp == 'b2g' || toolkit == 'android' || e10s #TIMED_OUT # b2g(window.open focus issues (using fullscreen)) b2g-debug(window.open focus issues (using fullscreen)) b2g-desktop(window.open focus issues (using fullscreen))
|
||||
skip-if = toolkit == "windows" || buildapp == 'b2g' || toolkit == 'android' || e10s # B2G - window.open focus issues using fullscreen, Windows - bug 919106 & bug 931445
|
||||
|
22
dom/webidl/BroadcastChannel.webidl
Normal file
22
dom/webidl/BroadcastChannel.webidl
Normal file
@ -0,0 +1,22 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* For more information on this interface, please see
|
||||
* http://www.whatwg.org/specs/web-apps/current-work/multipage/web-messaging.html#broadcasting-to-other-browsing-contexts
|
||||
*/
|
||||
|
||||
[Constructor(DOMString channel),
|
||||
Exposed=(Window,Worker),
|
||||
Func="BroadcastChannel::IsEnabled"]
|
||||
interface BroadcastChannel : EventTarget {
|
||||
readonly attribute DOMString name;
|
||||
|
||||
[Throws]
|
||||
void postMessage(any message);
|
||||
|
||||
void close();
|
||||
|
||||
attribute EventHandler onmessage;
|
||||
};
|
@ -55,6 +55,7 @@ WEBIDL_FILES = [
|
||||
'BiquadFilterNode.webidl',
|
||||
'Blob.webidl',
|
||||
'BoxObject.webidl',
|
||||
'BroadcastChannel.webidl',
|
||||
'BrowserElement.webidl',
|
||||
'BrowserElementDictionaries.webidl',
|
||||
'CallsList.webidl',
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user