Merge m-c to b2g-inbound a=merge

This commit is contained in:
Wes Kocher 2015-01-15 17:57:43 -08:00
commit 295f54f807
344 changed files with 11989 additions and 5641 deletions

View File

@ -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);

View File

@ -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,

View File

@ -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", {});
}
},
/**

View File

@ -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() {

View File

@ -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");
}

View File

@ -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();
}

View File

@ -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) {

View File

@ -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]

View File

@ -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");
});

View File

@ -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");
});

View File

@ -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>

View File

@ -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]

View File

@ -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]

View File

@ -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() {

View File

@ -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;
});

View File

@ -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]

View File

@ -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;

View File

@ -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);
});
}

View File

@ -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();
});

View File

@ -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();
});

View File

@ -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>

View File

@ -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

View File

@ -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",

View File

@ -347,8 +347,6 @@ InspectorPanel.prototype = {
"animationinspector" == defaultTab);
}
let ruleViewTab = this.sidebar.getTab("ruleview");
this.sidebar.show();
},

View File

@ -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.
*/

View File

@ -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]

View File

@ -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);
}
});

View File

@ -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");
}

View File

@ -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");
}

View File

@ -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);
}
}

View File

@ -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);
},
/**

View File

@ -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.");
});

View File

@ -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();
}

View File

@ -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)"

View File

@ -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

View File

@ -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
################################################################################

View File

@ -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.

View File

@ -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

View File

@ -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;
}
}
};

View File

@ -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 */

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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;

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View File

@ -0,0 +1,11 @@
onmessage = function() {
var exists = true;
try {
var bc = new BroadcastChannel('foobar');
} catch(e) {
exists = false;
}
postMessage({ exists: exists });
}

View 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");
}
}

View 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");
}

View File

@ -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);

View File

@ -0,0 +1,5 @@
(new BroadcastChannel('foobar')).onmessage = function(event) {
event.target.postMessage(event.data);
}
postMessage("READY");

View 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>

View 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]

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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);

View File

@ -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

View File

@ -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

View File

@ -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)) {

View File

@ -164,7 +164,6 @@ protected:
static bool sInstalledMenuKeyboardListener;
static bool sIsTestingIME;
static bool sIsGettingNewIMEState;
static bool sCheckForIMEUnawareWebApps;
class MOZ_STACK_CLASS GettingNewIMEStateBlocker MOZ_FINAL
{

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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 =

View File

@ -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

View File

@ -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",

View File

@ -45,6 +45,8 @@ public:
return false;
}
void ClearFailedRead() { mFailedRead.reset(); }
void Pin()
{
mResource->Pin();

View File

@ -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)
{

View File

@ -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);

View File

@ -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;

View File

@ -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");

View File

@ -535,7 +535,7 @@ var commandsPeerConnection = [
test.pcLocal.checkStatsIceConnections(stats,
test._offer_constraints,
test._offer_options,
test.originalAnswer);
test._remote_answer);
test.next();
});
}

View File

@ -93,6 +93,7 @@ DIRS += [
'workers',
'camera',
'audiochannel',
'broadcastchannel',
'promise',
'smil',
'telephony',

View File

@ -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)
{

View File

@ -32,6 +32,7 @@ public:
virtual bool RecvRequestDelete() MOZ_OVERRIDE;
uint32_t GetAppId();
bool GetInBrowser();
void AddIPDLReference();
void ReleaseIPDLReference();

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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));

View File

@ -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);

View File

@ -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);

View File

@ -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!

View File

@ -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

View 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;
};

View File

@ -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