author | Carsten "Tomcat" Book <cbook@mozilla.com> |
Thu, 24 Sep 2015 12:03:40 +0200 | |
changeset 264199 | 001942e4617b2324bfa6cdfb1155581cbc3f0cc4 |
parent 264198 | bf2bc1aa78c0b72d9b6b13f7a8c6ae61c60a51dc (current diff) |
parent 264131 | c0b4cc5553c0ba865b904538c42a6cdb775e5762 (diff) |
child 264200 | 0d7793440e17f415ef4937db7fbf0abc679d4f66 |
child 264271 | 791c94937a878417bd6f210caa49b5972db1bf47 |
child 264285 | 4d86ae08548baf2cd2cab3e4e58084dd912cfad3 |
push id | 65548 |
push user | cbook@mozilla.com |
push date | Thu, 24 Sep 2015 10:06:18 +0000 |
treeherder | mozilla-inbound@0d7793440e17 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 44.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/components/extensions/ext-browserAction.js +++ b/browser/components/extensions/ext-browserAction.js @@ -9,16 +9,21 @@ var { DefaultWeakMap, ignoreEvent, runSafe, } = ExtensionUtils; // WeakMap[Extension -> BrowserAction] var browserActionMap = new WeakMap(); +// WeakMap[Extension -> docshell] +// This map is a cache of the windowless browser that's used to render ImageData +// for the browser_action icon. +let imageRendererMap = new WeakMap(); + function browserActionOf(extension) { return browserActionMap.get(extension); } function makeWidgetId(id) { id = id.toLowerCase(); @@ -36,19 +41,16 @@ function BrowserAction(options, extensio this.widget = null; this.title = new DefaultWeakMap(extension.localize(options.default_title)); this.badgeText = new DefaultWeakMap(); this.badgeBackgroundColor = new DefaultWeakMap(); this.icon = new DefaultWeakMap(options.default_icon); this.popup = new DefaultWeakMap(options.default_popup); - // Make the default something that won't compare equal to anything. - this.prevPopups = new DefaultWeakMap({}); - this.context = null; } BrowserAction.prototype = { build() { let widget = CustomizableUI.createWidget({ id: this.id, type: "custom", @@ -58,32 +60,109 @@ BrowserAction.prototype = { let node = document.createElement("toolbarbutton"); node.id = this.id; node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional badged-button"); node.setAttribute("constrain-size", "true"); this.updateTab(null, node); let tabbrowser = document.defaultView.gBrowser; - tabbrowser.ownerDocument.addEventListener("TabSelect", () => { - this.updateTab(tabbrowser.selectedTab, node); - }); + tabbrowser.tabContainer.addEventListener("TabSelect", this); node.addEventListener("command", event => { - if (node.getAttribute("type") != "panel") { + let tab = tabbrowser.selectedTab; + let popup = this.getProperty(tab, "popup"); + if (popup) { + this.togglePopup(node, popup); + } else { this.emit("click"); } }); return node; }, }); this.widget = widget; }, + handleEvent(event) { + if (event.type == "TabSelect") { + let window = event.target.ownerDocument.defaultView; + let tabbrowser = window.gBrowser; + let instance = CustomizableUI.getWidget(this.id).forWindow(window); + if (instance) { + this.updateTab(tabbrowser.selectedTab, instance.node); + } + } + }, + + togglePopup(node, popupResource) { + let popupURL = this.extension.baseURI.resolve(popupResource); + + let document = node.ownerDocument; + let panel = document.createElement("panel"); + panel.setAttribute("class", "browser-action-panel"); + panel.setAttribute("type", "arrow"); + panel.setAttribute("flip", "slide"); + node.appendChild(panel); + + panel.addEventListener("popuphidden", () => { + this.context.unload(); + this.context = null; + panel.remove(); + }); + + const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + let browser = document.createElementNS(XUL_NS, "browser"); + browser.setAttribute("type", "content"); + browser.setAttribute("disableglobalhistory", "true"); + panel.appendChild(browser); + + let loadListener = () => { + panel.removeEventListener("load", loadListener); + + this.context = new ExtensionPage(this.extension, { + type: "popup", + contentWindow: browser.contentWindow, + uri: Services.io.newURI(popupURL, null, null), + docShell: browser.docShell, + }); + GlobalManager.injectInDocShell(browser.docShell, this.extension, this.context); + browser.setAttribute("src", popupURL); + + let contentLoadListener = () => { + browser.removeEventListener("load", contentLoadListener); + + let contentViewer = browser.docShell.contentViewer; + let width = {}, height = {}; + try { + contentViewer.getContentSize(width, height); + [width, height] = [width.value, height.value]; + } catch (e) { + // getContentSize can throw + [width, height] = [400, 400]; + } + + let window = document.defaultView; + width /= window.devicePixelRatio; + height /= window.devicePixelRatio; + width = Math.min(width, 800); + height = Math.min(height, 800); + + browser.setAttribute("width", width); + browser.setAttribute("height", height); + + let anchor = document.getAnonymousElementByAttribute(node, "class", "toolbarbutton-icon"); + panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false); + }; + browser.addEventListener("load", contentLoadListener, true); + }; + panel.addEventListener("load", loadListener); + }, + // Initialize the toolbar icon and popup given that |tab| is the // current tab and |node| is the CustomizableUI node. Note: |tab| // will be null if we don't know the current tab yet (during // initialization). updateTab(tab, node) { let window = node.ownerDocument.defaultView; let title = this.getProperty(tab, "title"); @@ -113,67 +192,16 @@ BrowserAction.prototype = { if (Array.isArray(color)) { color = `rgb(${color[0]}, ${color[1]}, ${color[2]})`; } badgeNode.style.backgroundColor = color; } let iconURL = this.getIcon(tab, node); node.setAttribute("image", iconURL); - - let popup = this.getProperty(tab, "popup"); - - if (popup != this.prevPopups.get(window)) { - this.prevPopups.set(window, popup); - - let panel = node.querySelector("panel"); - if (panel) { - panel.remove(); - } - - if (popup) { - let popupURL = this.extension.baseURI.resolve(popup); - node.setAttribute("type", "panel"); - - let document = node.ownerDocument; - let panel = document.createElement("panel"); - panel.setAttribute("class", "browser-action-panel"); - panel.setAttribute("type", "arrow"); - panel.setAttribute("flip", "slide"); - node.appendChild(panel); - - const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; - let browser = document.createElementNS(XUL_NS, "browser"); - browser.setAttribute("type", "content"); - browser.setAttribute("disableglobalhistory", "true"); - browser.setAttribute("width", "500"); - browser.setAttribute("height", "500"); - panel.appendChild(browser); - - let loadListener = () => { - panel.removeEventListener("load", loadListener); - - if (this.context) { - this.context.unload(); - } - - this.context = new ExtensionPage(this.extension, { - type: "popup", - contentWindow: browser.contentWindow, - uri: Services.io.newURI(popupURL, null, null), - docShell: browser.docShell, - }); - GlobalManager.injectInDocShell(browser.docShell, this.extension, this.context); - browser.setAttribute("src", popupURL); - }; - panel.addEventListener("load", loadListener); - } else { - node.removeAttribute("type"); - } - } }, // Note: tab is allowed to be null here. getIcon(tab, node) { let icon = this.icon.get(tab); let url; if (typeof(icon) != "object") { @@ -231,16 +259,23 @@ BrowserAction.prototype = { // tab is allowed to be null. // prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor". getProperty(tab, prop) { return this[prop].get(tab); }, shutdown() { + let widget = CustomizableUI.getWidget(this.id); + for (let instance of widget.instances) { + let window = instance.node.ownerDocument.defaultView; + let tabbrowser = window.gBrowser; + tabbrowser.tabContainer.removeEventListener("TabSelect", this); + } + CustomizableUI.destroyWidget(this.id); }, }; EventEmitter.decorate(BrowserAction.prototype); extensions.on("manifest_browser_action", (type, directive, extension, manifest) => { let browserAction = new BrowserAction(manifest.browser_action, extension); @@ -248,18 +283,47 @@ extensions.on("manifest_browser_action", browserActionMap.set(extension, browserAction); }); extensions.on("shutdown", (type, extension) => { if (browserActionMap.has(extension)) { browserActionMap.get(extension).shutdown(); browserActionMap.delete(extension); } + imageRendererMap.delete(extension); }); +function convertImageDataToPNG(extension, imageData) +{ + let webNav = imageRendererMap.get(extension); + if (!webNav) { + webNav = Services.appShell.createWindowlessBrowser(false); + let principal = Services.scriptSecurityManager.createCodebasePrincipal(extension.baseURI, + {addonId: extension.id}); + let interfaceRequestor = webNav.QueryInterface(Ci.nsIInterfaceRequestor); + let docShell = interfaceRequestor.getInterface(Ci.nsIDocShell); + + GlobalManager.injectInDocShell(docShell, extension, null); + + docShell.createAboutBlankContentViewer(principal); + } + + let document = webNav.document; + let canvas = document.createElement("canvas"); + canvas.width = imageData.width; + canvas.height = imageData.height; + canvas.getContext("2d").putImageData(imageData, 0, 0); + + let url = canvas.toDataURL("image/png"); + + canvas.remove(); + + return url; +} + extensions.registerAPI((extension, context) => { return { browserAction: { onClicked: new EventManager(context, "browserAction.onClicked", fire => { let listener = () => { let tab = TabManager.activeTab; fire(TabManager.convert(extension, tab)); }; @@ -278,20 +342,21 @@ extensions.registerAPI((extension, conte let tab = details.tabId ? TabManager.getTab(details.tabId) : null; let title = browserActionOf(extension).getProperty(tab, "title"); runSafe(context, callback, title); }, setIcon: function(details, callback) { let tab = details.tabId ? TabManager.getTab(details.tabId) : null; if (details.imageData) { - // FIXME: Support the imageData attribute. - return; + let url = convertImageDataToPNG(extension, details.imageData); + browserActionOf(extension).setProperty(tab, "icon", url); + } else { + browserActionOf(extension).setProperty(tab, "icon", details.path); } - browserActionOf(extension).setProperty(tab, "icon", details.path); }, setBadgeText: function(details) { let tab = details.tabId ? TabManager.getTab(details.tabId) : null; browserActionOf(extension).setProperty(tab, "badgeText", details.text); }, getBadgeText: function(details, callback) {
--- a/browser/components/extensions/ext-tabs.js +++ b/browser/components/extensions/ext-tabs.js @@ -37,51 +37,69 @@ function getSender(context, target, send // WeakMap[ExtensionPage -> {tab, parentWindow}] var pageDataMap = new WeakMap(); // This listener fires whenever an extension page opens in a tab // (either initiated by the extension or the user). Its job is to fill // in some tab-specific details and keep data around about the // ExtensionPage. extensions.on("page-load", (type, page, params, sender, delegate) => { - if (params.type == "tab") { + if (params.type == "tab" || params.type == "popup") { let browser = params.docShell.chromeEventHandler; + let parentWindow = browser.ownerDocument.defaultView; - let tab = parentWindow.gBrowser.getTabForBrowser(browser); - sender.tabId = TabManager.getId(tab); + page.windowId = WindowManager.getId(parentWindow); + + let tab = null; + if (params.type == "tab") { + tab = parentWindow.gBrowser.getTabForBrowser(browser); + sender.tabId = TabManager.getId(tab); + page.tabId = TabManager.getId(tab); + } pageDataMap.set(page, {tab, parentWindow}); } delegate.getSender = getSender; }); extensions.on("page-unload", (type, page) => { pageDataMap.delete(page); }); extensions.on("page-shutdown", (type, page) => { if (pageDataMap.has(page)) { let {tab, parentWindow} = pageDataMap.get(page); pageDataMap.delete(page); - parentWindow.gBrowser.removeTab(tab); + if (tab) { + parentWindow.gBrowser.removeTab(tab); + } } }); extensions.on("fill-browser-data", (type, browser, data, result) => { let tabId = TabManager.getBrowserId(browser); if (tabId == -1) { result.cancel = true; return; } data.tabId = tabId; }); +global.currentWindow = function(context) +{ + let pageData = pageDataMap.get(context); + if (pageData) { + return pageData.parentWindow; + } + return WindowManager.topWindow; +} + // TODO: activeTab permission extensions.registerAPI((extension, context) => { let self = { tabs: { onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => { let tab = event.originalTarget; let tabId = TabManager.getId(tab); @@ -252,17 +270,17 @@ extensions.registerAPI((extension, conte window.gBrowser.pinTab(tab); } if (callback) { runSafe(context, callback, TabManager.convert(extension, tab)); } } - let window = createProperties.windowId ? + let window = "windowId" in createProperties ? WindowManager.getWindow(createProperties.windowId) : WindowManager.topWindow; if (!window.gBrowser) { let obs = (finishedWindow, topic, data) => { if (finishedWindow != window) { return; } Services.obs.removeObserver(obs, "browser-delayed-startup-finished"); @@ -377,40 +395,43 @@ extensions.registerAPI((extension, conte let windowType = WindowManager.windowType(window); if ("windowType" in queryInfo && queryInfo.windowType != windowType) { return false; } if ("windowId" in queryInfo) { if (queryInfo.windowId == WindowManager.WINDOW_ID_CURRENT) { - if (context.contentWindow != window) { + if (currentWindow(context) != window) { return false; } } else { if (queryInfo.windowId != tab.windowId) { return false; } } } if ("currentWindow" in queryInfo) { - let eq = window == context.contentWindow; + let eq = window == currentWindow(context); if (queryInfo.currentWindow != eq) { return false; } } return true; } let result = []; let e = Services.wm.getEnumerator("navigator:browser"); while (e.hasMoreElements()) { let window = e.getNext(); + if (window.document.readyState != "complete") { + continue; + } let tabs = TabManager.getTabs(extension, window); for (let tab of tabs) { if (matches(window, tab)) { result.push(tab); } } } runSafe(context, callback, result);
--- a/browser/components/extensions/ext-windows.js +++ b/browser/components/extensions/ext-windows.js @@ -40,17 +40,21 @@ extensions.registerAPI((extension, conte }).api(), get: function(windowId, getInfo, callback) { let window = WindowManager.getWindow(windowId); runSafe(context, callback, WindowManager.convert(extension, window, getInfo)); }, getCurrent: function(getInfo, callback) { - let window = context.contentWindow; + if (!callback) { + callback = getInfo; + getInfo = {}; + } + let window = currentWindow(context); runSafe(context, callback, WindowManager.convert(extension, window, getInfo)); }, getLastFocused: function(...args) { let getInfo, callback; if (args.length == 1) { callback = args[0]; } else { @@ -60,17 +64,19 @@ extensions.registerAPI((extension, conte runSafe(context, callback, WindowManager.convert(extension, window, getInfo)); }, getAll: function(getInfo, callback) { let e = Services.wm.getEnumerator("navigator:browser"); let windows = []; while (e.hasMoreElements()) { let window = e.getNext(); - windows.push(WindowManager.convert(extension, window, getInfo)); + if (window.document.readyState == "complete") { + windows.push(WindowManager.convert(extension, window, getInfo)); + } } runSafe(context, callback, windows); }, create: function(createData, callback) { function mkstr(s) { let result = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString); result.data = s;
--- a/browser/components/extensions/test/browser/browser.ini +++ b/browser/components/extensions/test/browser/browser.ini @@ -1,9 +1,14 @@ [DEFAULT] skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac' +support-files = + head.js -[browser_extensions_simple.js] +[browser_ext_simple.js] +[browser_ext_currentWindow.js] [browser_ext_browserAction_simple.js] +[browser_ext_browserAction_icon.js] +[browser_ext_getViews.js] [browser_ext_tabs_executeScript.js] [browser_ext_tabs_query.js] [browser_ext_tabs_update.js] [browser_ext_windows_update.js]
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_browserAction_icon.js @@ -0,0 +1,40 @@ +add_task(function* () { + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "browser_action": {}, + "background": { + "page": "background.html", + } + }, + + files: { + "background.html": `<canvas id="canvas" width="2" height="2"> + <script src="background.js"></script>`, + + "background.js": function() { + var canvas = document.getElementById("canvas"); + var canvasContext = canvas.getContext("2d"); + + canvasContext.clearRect(0, 0, canvas.width, canvas.height); + canvasContext.fillStyle = "green"; + canvasContext.fillRect(0, 0, 1, 1); + + var url = canvas.toDataURL("image/png"); + var imageData = canvasContext.getImageData(0, 0, canvas.width, canvas.height); + browser.browserAction.setIcon({imageData}); + + browser.test.sendMessage("imageURL", url); + } + }, + }); + + let [_, url] = yield Promise.all([extension.startup(), extension.awaitMessage("imageURL")]); + + let widgetId = makeWidgetId(extension.id) + "-browser-action"; + let node = CustomizableUI.getWidget(widgetId).forWindow(window).node; + + let image = node.getAttribute("image"); + is(image, url, "image is correct"); + + yield extension.unload(); +});
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js +++ b/browser/components/extensions/test/browser/browser_ext_browserAction_simple.js @@ -17,20 +17,36 @@ add_task(function* () { "popup.js": function() { browser.runtime.sendMessage("from-popup"); } }, background: function() { browser.runtime.onMessage.addListener(msg => { browser.test.assertEq(msg, "from-popup", "correct message received"); - browser.test.notifyPass("browser_action.simple"); + browser.test.sendMessage("popup"); }); }, }); yield extension.startup(); - // FIXME: Should really test opening the popup here. + let widgetId = makeWidgetId(extension.id) + "-browser-action"; + let node = CustomizableUI.getWidget(widgetId).forWindow(window).node; - yield extension.awaitFinish("browser_action.simple"); + // Do this a few times to make sure the pop-up is reloaded each time. + for (let i = 0; i < 3; i++) { + let evt = new CustomEvent("command", { + bubbles: true, + cancelable: true + }); + node.dispatchEvent(evt); + + yield extension.awaitMessage("popup"); + + let panel = node.querySelector("panel"); + if (panel) { + panel.hidePopup(); + } + } + yield extension.unload(); });
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js @@ -0,0 +1,173 @@ +function* focusWindow(win) +{ + let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager); + if (fm.activeWindow == win) { + return; + } + + let promise = new Promise(resolve => { + win.addEventListener("focus", function listener() { + win.removeEventListener("focus", listener, true); + resolve(); + }, true); + }); + + win.focus(); + yield promise; +} + +function genericChecker() +{ + var kind = "background"; + var path = window.location.pathname; + if (path.indexOf("popup") != -1) { + kind = "popup"; + } else if (path.indexOf("page") != -1) { + kind = "page"; + } + + browser.test.onMessage.addListener((msg, ...args) => { + if (msg == kind + "-check-current1") { + browser.tabs.query({ + currentWindow: true + }, function(tabs) { + browser.test.sendMessage("result", tabs[0].windowId); + }); + } else if (msg == kind + "-check-current2") { + browser.tabs.query({ + windowId: browser.windows.WINDOW_ID_CURRENT + }, function(tabs) { + browser.test.sendMessage("result", tabs[0].windowId); + }); + } else if (msg == kind + "-check-current3") { + browser.windows.getCurrent(function(window) { + browser.test.sendMessage("result", window.id); + }); + } else if (msg == kind + "-open-page") { + browser.tabs.create({windowId: args[0], url: chrome.runtime.getURL("page.html")}); + } else if (msg == kind + "-close-page") { + browser.tabs.query({ + windowId: args[0], + }, tabs => { + var tab = tabs.find(tab => tab.url.indexOf("page.html") != -1); + browser.tabs.remove(tab.id, () => { + browser.test.sendMessage("closed"); + }); + }); + } + }); + browser.test.sendMessage(kind + "-ready"); +} + +add_task(function* () { + let win1 = yield BrowserTestUtils.openNewBrowserWindow(); + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + + yield focusWindow(win2); + + yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots"); + yield BrowserTestUtils.browserLoaded(win1.gBrowser.selectedBrowser); + + yield BrowserTestUtils.loadURI(win2.gBrowser.selectedBrowser, "about:config"); + yield BrowserTestUtils.browserLoaded(win2.gBrowser.selectedBrowser); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "permissions": ["tabs"], + + "browser_action": { + "default_popup": "popup.html" + }, + }, + + files: { + "page.html": ` + <!DOCTYPE html> + <html><body> + <script src="page.js"></script> + </body></html> + `, + + "page.js": genericChecker, + + "popup.html": ` + <!DOCTYPE html> + <html><body> + <script src="popup.js"></script> + </body></html> + `, + + "popup.js": genericChecker, + }, + + background: genericChecker, + }); + + yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]); + + let {TabManager, WindowManager} = Cu.import("resource://gre/modules/Extension.jsm", {}); + + let winId1 = WindowManager.getId(win1); + let winId2 = WindowManager.getId(win2); + + function* checkWindow(kind, winId, name) { + extension.sendMessage(kind + "-check-current1"); + is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`); + extension.sendMessage(kind + "-check-current2"); + is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`); + extension.sendMessage(kind + "-check-current3"); + is((yield extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`); + } + + yield focusWindow(win1); + yield checkWindow("background", winId1, "win1"); + yield focusWindow(win2); + yield checkWindow("background", winId2, "win2"); + + function* triggerPopup(win, callback) { + let widgetId = makeWidgetId(extension.id) + "-browser-action"; + let node = CustomizableUI.getWidget(widgetId).forWindow(win).node; + + let evt = new CustomEvent("command", { + bubbles: true, + cancelable: true + }); + node.dispatchEvent(evt); + + yield extension.awaitMessage("popup-ready"); + + yield callback(); + + let panel = node.querySelector("panel"); + if (panel) { + panel.hidePopup(); + } + } + + // Set focus to some other window. + yield focusWindow(window); + + yield triggerPopup(win1, function*() { + yield checkWindow("popup", winId1, "win1"); + }); + + yield triggerPopup(win2, function*() { + yield checkWindow("popup", winId2, "win2"); + }); + + function* triggerPage(winId, name) { + extension.sendMessage("background-open-page", winId); + yield extension.awaitMessage("page-ready"); + yield checkWindow("page", winId, name); + extension.sendMessage("background-close-page", winId); + yield extension.awaitMessage("closed"); + } + + yield triggerPage(winId1, "win1"); + yield triggerPage(winId2, "win2"); + + yield extension.unload(); + + yield BrowserTestUtils.closeWindow(win1); + yield BrowserTestUtils.closeWindow(win2); +});
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/browser_ext_getViews.js @@ -0,0 +1,178 @@ +function genericChecker() +{ + var kind = "background"; + var path = window.location.pathname; + if (path.indexOf("popup") != -1) { + kind = "popup"; + } else if (path.indexOf("tab") != -1) { + kind = "tab"; + } + window.kind = kind; + + browser.test.onMessage.addListener((msg, ...args) => { + if (msg == kind + "-check-views") { + var views = browser.extension.getViews(); + var counts = { + "background": 0, + "tab": 0, + "popup": 0 + }; + for (var i = 0; i < views.length; i++) { + var view = views[i]; + browser.test.assertTrue(view.kind in counts, "view type is valid"); + counts[view.kind]++; + if (view.kind == "background") { + browser.test.assertTrue(view === browser.extension.getBackgroundPage(), + "background page is correct"); + } + } + browser.test.sendMessage("counts", counts); + } else if (msg == kind + "-open-tab") { + browser.tabs.create({windowId: args[0], url: chrome.runtime.getURL("tab.html")}); + } else if (msg == kind + "-close-tab") { + browser.tabs.query({ + windowId: args[0], + }, tabs => { + var tab = tabs.find(tab => tab.url.indexOf("tab.html") != -1); + browser.tabs.remove(tab.id, () => { + browser.test.sendMessage("closed"); + }); + }); + } + }); + browser.test.sendMessage(kind + "-ready"); +} + +add_task(function* () { + let win1 = yield BrowserTestUtils.openNewBrowserWindow(); + let win2 = yield BrowserTestUtils.openNewBrowserWindow(); + + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + "permissions": ["tabs"], + + "browser_action": { + "default_popup": "popup.html" + }, + }, + + files: { + "tab.html": ` + <!DOCTYPE html> + <html><body> + <script src="tab.js"></script> + </body></html> + `, + + "tab.js": genericChecker, + + "popup.html": ` + <!DOCTYPE html> + <html><body> + <script src="popup.js"></script> + </body></html> + `, + + "popup.js": genericChecker, + }, + + background: genericChecker, + }); + + yield Promise.all([extension.startup(), extension.awaitMessage("background-ready")]); + + info("started"); + + let {TabManager, WindowManager} = Cu.import("resource://gre/modules/Extension.jsm", {}); + + let winId1 = WindowManager.getId(win1); + let winId2 = WindowManager.getId(win2); + + function* openTab(winId) { + extension.sendMessage("background-open-tab", winId); + yield extension.awaitMessage("tab-ready"); + } + + function* checkViews(kind, tabCount, popupCount) { + extension.sendMessage(kind + "-check-views"); + let counts = yield extension.awaitMessage("counts"); + is(counts.background, 1, "background count correct"); + is(counts.tab, tabCount, "tab count correct"); + is(counts.popup, popupCount, "popup count correct"); + } + + yield checkViews("background", 0, 0); + + yield openTab(winId1); + + yield checkViews("background", 1, 0); + yield checkViews("tab", 1, 0); + + yield openTab(winId2); + + yield checkViews("background", 2, 0); + + function* triggerPopup(win, callback) { + let widgetId = makeWidgetId(extension.id) + "-browser-action"; + let node = CustomizableUI.getWidget(widgetId).forWindow(win).node; + + let evt = new CustomEvent("command", { + bubbles: true, + cancelable: true + }); + node.dispatchEvent(evt); + + yield extension.awaitMessage("popup-ready"); + + yield callback(); + + let panel = node.querySelector("panel"); + if (panel) { + panel.hidePopup(); + } + } + + yield triggerPopup(win1, function*() { + yield checkViews("background", 2, 1); + yield checkViews("popup", 2, 1); + }); + + yield triggerPopup(win2, function*() { + yield checkViews("background", 2, 1); + yield checkViews("popup", 2, 1); + }); + + info("checking counts after popups"); + + yield checkViews("background", 2, 0); + + info("closing one tab"); + + extension.sendMessage("background-close-tab", winId1); + yield extension.awaitMessage("closed"); + + info("one tab closed, one remains"); + + yield checkViews("background", 1, 0); + + info("opening win1 popup"); + + yield triggerPopup(win1, function*() { + yield checkViews("background", 1, 1); + yield checkViews("tab", 1, 1); + yield checkViews("popup", 1, 1); + }); + + info("opening win2 popup"); + + yield triggerPopup(win2, function*() { + yield checkViews("background", 1, 1); + yield checkViews("tab", 1, 1); + yield checkViews("popup", 1, 1); + }); + + yield extension.unload(); + + yield BrowserTestUtils.closeWindow(win1); + yield BrowserTestUtils.closeWindow(win2); +});
rename from browser/components/extensions/test/browser/browser_extensions_simple.js rename to browser/components/extensions/test/browser/browser_ext_simple.js
new file mode 100644 --- /dev/null +++ b/browser/components/extensions/test/browser/head.js @@ -0,0 +1,7 @@ +let {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm"); + +function makeWidgetId(id) +{ + id = id.toLowerCase(); + return id.replace(/[^a-z0-9_-]/g, "_"); +}
--- a/configure.in +++ b/configure.in @@ -7246,16 +7246,19 @@ if test -z "$MOZ_MEMORY"; then *-mingw*) if test -z "$WIN32_REDIST_DIR" -a -z "$MOZ_DEBUG"; then AC_MSG_WARN([When not building jemalloc, you need to set WIN32_REDIST_DIR to the path to the Visual C++ Redist (usually VCINSTALLDIR\redist\x86\Microsoft.VC80.CRT, for VC++ v8) if you intend to distribute your build.]) fi ;; esac else AC_DEFINE(MOZ_MEMORY) + if test -n "$NIGHTLY_BUILD"; then + MOZ_JEMALLOC4=1 + fi if test -n "$MOZ_JEMALLOC4"; then AC_DEFINE(MOZ_JEMALLOC4) fi if test "x$MOZ_DEBUG" = "x1"; then AC_DEFINE(MOZ_MEMORY_DEBUG) fi dnl The generic feature tests that determine how to compute ncpus are long and dnl complicated. Therefore, simply define special cpp variables for the
--- a/devtools/client/webconsole/test/browser_webconsole_console_logging_workers_api.js +++ b/devtools/client/webconsole/test/browser_webconsole_console_logging_workers_api.js @@ -4,27 +4,17 @@ // Tests that the basic console.log()-style APIs and filtering work for // sharedWorkers "use strict"; const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" + "test/test-console-workers.html"; -function pushPrefEnv() { - let deferred = promise.defer(); - let options = { - set: [["dom.workers.sharedWorkers.enabled", true]] - }; - SpecialPowers.pushPrefEnv(options, deferred.resolve); - return deferred.promise; -} - var test = asyncTest(function*() { - yield pushPrefEnv(); yield loadTab(TEST_URI); let hud = yield openConsole(); yield waitForMessages({ webconsole: hud, messages: [{ text: "foo-bar-shared-worker"
--- a/devtools/shared/heapsnapshot/DeserializedNode.cpp +++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp @@ -103,18 +103,18 @@ class DeserializedEdgeRange : public Edg EdgeVector edges; size_t i; void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; } public: - explicit DeserializedEdgeRange(JSContext* cx) - : edges(cx) + explicit DeserializedEdgeRange() + : edges() , i(0) { settle(); } bool init(DeserializedNode& node) { if (!edges.reserve(node.edges.length())) @@ -155,20 +155,20 @@ Concrete<DeserializedNode>::allocationSt MOZ_ASSERT(ptr); // See above comment in DeserializedNode::getEdgeReferent about why this // const_cast is needed and safe. return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr)); } UniquePtr<EdgeRange> -Concrete<DeserializedNode>::edges(JSContext* cx, bool) const +Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const { UniquePtr<DeserializedEdgeRange, JS::DeletePolicy<DeserializedEdgeRange>> range( - js_new<DeserializedEdgeRange>(cx)); + js_new<DeserializedEdgeRange>()); if (!range || !range->init(get())) return nullptr; return UniquePtr<EdgeRange>(range.release()); } StackFrame
--- a/devtools/shared/heapsnapshot/DeserializedNode.h +++ b/devtools/shared/heapsnapshot/DeserializedNode.h @@ -260,17 +260,17 @@ public: Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override; const char* jsObjectClassName() const override { return get().jsObjectClassName.get(); } bool hasAllocationStack() const override { return get().allocationStack.isSome(); } StackFrame allocationStack() const override; // We ignore the `bool wantNames` parameter because we can't control whether // the core dump was serialized with edge names or not. - UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override; + UniquePtr<EdgeRange> edges(JSRuntime* rt, bool) const override; }; template<> class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame { protected: explicit ConcreteStackFrame(DeserializedStackFrame* ptr) : BaseStackFrame(ptr)
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp +++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp @@ -379,17 +379,17 @@ HeapSnapshot::TakeCensus(JSContext* cx, return; } JS::ubi::CensusHandler handler(census, rootCount); { JS::AutoCheckCannotGC nogc; - JS::ubi::CensusTraversal traversal(cx, handler, nogc); + JS::ubi::CensusTraversal traversal(JS_GetRuntime(cx), handler, nogc); if (NS_WARN_IF(!traversal.init())) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } if (NS_WARN_IF(!traversal.addStart(getRoot()))) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return; @@ -679,17 +679,17 @@ public: } if (auto className = ubiNode.jsObjectClassName()) { size_t length = strlen(className); protobufNode.set_jsobjectclassname(className, length); } if (includeEdges) { - auto edges = ubiNode.edges(cx, wantNames); + auto edges = ubiNode.edges(JS_GetRuntime(cx), wantNames); if (NS_WARN_IF(!edges)) return false; for ( ; !edges->empty(); edges->popFront()) { const ubi::Edge& ubiEdge = edges->front(); protobuf::Edge* protobufEdge = protobufNode.add_edges(); if (NS_WARN_IF(!protobufEdge)) { @@ -790,17 +790,17 @@ WriteHeapGraph(JSContext* cx, if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) { return false; } // Walk the heap graph starting from the given node and serialize it into the // core dump. HeapSnapshotHandler handler(writer, zones); - HeapSnapshotHandler::Traversal traversal(cx, handler, noGC); + HeapSnapshotHandler::Traversal traversal(JS_GetRuntime(cx), handler, noGC); if (!traversal.init()) return false; traversal.wantNames = wantNames; bool ok = traversal.addStartVisited(node) && traversal.traverse(); if (ok) { @@ -955,17 +955,17 @@ ThreadSafeChromeUtils::SaveHeapSnapshot( StreamWriter writer(cx, gzipStream, wantNames); if (NS_WARN_IF(!writer.init())) { rv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } { Maybe<AutoCheckCannotGC> maybeNoGC; - ubi::RootList rootList(cx, maybeNoGC, wantNames); + ubi::RootList rootList(JS_GetRuntime(cx), maybeNoGC, wantNames); if (!EstablishBoundaries(cx, rv, boundaries, rootList, zones)) return; MOZ_ASSERT(maybeNoGC.isSome()); ubi::Node roots(&rootList); // Serialize the initial heap snapshot metadata to the core dump. if (!writer.writeMetadata(PR_Now()) ||
--- a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp +++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp @@ -92,10 +92,10 @@ DEF_TEST(DeserializedNodeUbiNodes, { edge3.referent = referent3->id; mocked.addEdge(Move(edge3)); EXPECT_CALL(mocked, getEdgeReferent(Field(&DeserializedEdge::referent, referent3->id))) .Times(1) .WillOnce(Return(JS::ubi::Node(referent3.get()))); - ubi.edges(cx); + ubi.edges(JS_GetRuntime(cx)); });
--- a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h +++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h @@ -157,18 +157,18 @@ struct DevTools : public ::testing::Test class MOZ_STACK_CLASS FakeNode { public: JS::ubi::EdgeVector edges; JSCompartment* compartment; JS::Zone* zone; size_t size; - explicit FakeNode(JSContext* cx) - : edges(cx), + explicit FakeNode() + : edges(), compartment(nullptr), zone(nullptr), size(1) { } }; namespace JS { namespace ubi { @@ -177,18 +177,18 @@ using mozilla::UniquePtr; template<> class Concrete<FakeNode> : public Base { const char16_t* typeName() const override { return concreteTypeName; } - UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override { - return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(cx, get().edges)); + UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override { + return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges)); } Size size(mozilla::MallocSizeOf) const override { return get().size; } JS::Zone* zone() const override { return get().zone; @@ -228,32 +228,32 @@ void AddEdge(FakeNode& node, FakeNode& r // Custom GMock Matchers // Use the testing namespace to avoid static analysis failures in the gmock // matcher classes that get generated from MATCHER_P macros. namespace testing { // Ensure that given node has the expected number of edges. -MATCHER_P2(EdgesLength, cx, expectedLength, "") { - auto edges = arg.edges(cx); +MATCHER_P2(EdgesLength, rt, expectedLength, "") { + auto edges = arg.edges(rt); if (!edges) return false; int actualLength = 0; for ( ; !edges->empty(); edges->popFront()) actualLength++; return Matcher<int>(Eq(expectedLength)) .MatchAndExplain(actualLength, result_listener); } // Get the nth edge and match it with the given matcher. -MATCHER_P3(Edge, cx, n, matcher, "") { - auto edges = arg.edges(cx); +MATCHER_P3(Edge, rt, n, matcher, "") { + auto edges = arg.edges(rt); if (!edges) return false; int i = 0; for ( ; !edges->empty(); edges->popFront()) { if (i == n) { return Matcher<const JS::ubi::Edge&>(matcher) .MatchAndExplain(edges->front(), result_listener);
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp +++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossZoneBoundaries.cpp @@ -24,20 +24,20 @@ DEF_TEST(DoesCrossZoneBoundaries, { ASSERT_NE(newZone, zone); // Our set of target zones is both the old and new zones. JS::ZoneSet targetZones; ASSERT_TRUE(targetZones.init()); ASSERT_TRUE(targetZones.put(zone)); ASSERT_TRUE(targetZones.put(newZone)); - FakeNode nodeA(cx); - FakeNode nodeB(cx); - FakeNode nodeC(cx); - FakeNode nodeD(cx); + FakeNode nodeA; + FakeNode nodeB; + FakeNode nodeC; + FakeNode nodeD; nodeA.zone = zone; nodeB.zone = nullptr; nodeC.zone = newZone; nodeD.zone = nullptr; AddEdge(nodeA, nodeB); AddEdge(nodeA, nodeC);
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp +++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossZoneBoundaries.cpp @@ -24,19 +24,19 @@ DEF_TEST(DoesntCrossZoneBoundaries, { ASSERT_NE(newZone, zone); // Our set of target zones is only the pre-existing zone and does not // include the new zone. JS::ZoneSet targetZones; ASSERT_TRUE(targetZones.init()); ASSERT_TRUE(targetZones.put(zone)); - FakeNode nodeA(cx); - FakeNode nodeB(cx); - FakeNode nodeC(cx); + FakeNode nodeA; + FakeNode nodeB; + FakeNode nodeC; nodeA.zone = zone; nodeB.zone = nullptr; nodeC.zone = newZone; AddEdge(nodeA, nodeB); AddEdge(nodeB, nodeC);
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp +++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp @@ -8,37 +8,37 @@ #include "DevTools.h" using testing::Field; using testing::IsNull; using testing::Property; using testing::Return; DEF_TEST(SerializesEdgeNames, { - FakeNode node(cx); - FakeNode referent(cx); + FakeNode node; + FakeNode referent; const char16_t edgeName[] = MOZ_UTF16("edge name"); const char16_t emptyStr[] = MOZ_UTF16(""); AddEdge(node, referent, edgeName); AddEdge(node, referent, emptyStr); AddEdge(node, referent, nullptr); ::testing::NiceMock<MockWriter> writer; // Should get the node with edges once. EXPECT_CALL( writer, - writeNode(AllOf(EdgesLength(cx, 3), - Edge(cx, 0, Field(&JS::ubi::Edge::name, + writeNode(AllOf(EdgesLength(rt, 3), + Edge(rt, 0, Field(&JS::ubi::Edge::name, UTF16StrEq(edgeName))), - Edge(cx, 1, Field(&JS::ubi::Edge::name, + Edge(rt, 1, Field(&JS::ubi::Edge::name, UTF16StrEq(emptyStr))), - Edge(cx, 2, Field(&JS::ubi::Edge::name, + Edge(rt, 2, Field(&JS::ubi::Edge::name, IsNull()))), _) ) .Times(1) .WillOnce(Return(true)); // Should get the referent node that doesn't have any edges once. ExpectWriteNode(writer, referent);
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp +++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp @@ -3,20 +3,20 @@ * 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/. */ // Test that everything in the heap graph gets serialized once, and only once. #include "DevTools.h" DEF_TEST(SerializesEverythingInHeapGraphOnce, { - FakeNode nodeA(cx); - FakeNode nodeB(cx); - FakeNode nodeC(cx); - FakeNode nodeD(cx); + FakeNode nodeA; + FakeNode nodeB; + FakeNode nodeC; + FakeNode nodeD; AddEdge(nodeA, nodeB); AddEdge(nodeB, nodeC); AddEdge(nodeC, nodeD); AddEdge(nodeD, nodeA); ::testing::NiceMock<MockWriter> writer;
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp +++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp @@ -6,17 +6,17 @@ // Test that a ubi::Node's typeName gets properly serialized into a core dump. #include "DevTools.h" using testing::Property; using testing::Return; DEF_TEST(SerializesTypeNames, { - FakeNode node(cx); + FakeNode node; ::testing::NiceMock<MockWriter> writer; EXPECT_CALL(writer, writeNode(Property(&JS::ubi::Node::typeName, UTF16StrEq(MOZ_UTF16("FakeNode"))), _)) .Times(1) .WillOnce(Return(true));
--- a/docshell/test/chrome/chrome.ini +++ b/docshell/test/chrome/chrome.ini @@ -75,16 +75,17 @@ skip-if = toolkit == "gtk2" [test_bug565388.xul] skip-if = os == 'linux' || os == 'mac' # Bug 1026815 [test_bug582176.xul] [test_bug608669.xul] [test_bug662200.xul] [test_bug690056.xul] [test_bug789773.xul] [test_bug846906.xul] +skip-if = os == 'linux' && asan # Bug 1207161 [test_bug89419.xul] [test_bug909218.html] [test_bug92598.xul] [test_mozFrameType.xul] [test_principalInherit.xul] [test_private_hidden_window.html] [test_viewsource_forbidden_in_iframe.xul] skip-if = true # bug 1019315
--- a/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html +++ b/dom/broadcastchannel/tests/test_broadcastchannel_sharedWorker.html @@ -39,14 +39,14 @@ function runTests() { bc.close(); SimpleTest.finish(); } worker.port.postMessage('go'); } SimpleTest.waitForExplicitFinish(); -SpecialPowers.pushPrefEnv({"set": [["dom.workers.sharedWorkers.enabled", true]]}, runTests); +runTests(); </script> </pre> </body> </html>
--- a/dom/canvas/CanvasRenderingContext2D.cpp +++ b/dom/canvas/CanvasRenderingContext2D.cpp @@ -941,16 +941,17 @@ CanvasRenderingContext2D::CanvasRenderin #ifdef USE_SKIA_GPU , mVideoTexture(0) #endif // these are the default values from the Canvas spec , mWidth(0), mHeight(0) , mZero(false), mOpaque(false) , mResetLayer(true) , mIPC(false) + , mIsSkiaGL(false) , mDrawObserver(nullptr) , mIsEntireFrameInvalid(false) , mPredictManyRedrawCalls(false) , mIsCapturedFrameInvalid(false) , mPathTransformWillUpdate(false) , mInvalidateCount(0) { sNumLivingContexts++; @@ -1371,16 +1372,18 @@ CanvasRenderingContext2D::EnsureTarget(R mTarget = mBufferProvider->GetDT(IntRect(IntPoint(), IntSize(mWidth, mHeight))); if (mTarget) { return mRenderingMode; } else { mBufferProvider = nullptr; } } + mIsSkiaGL = false; + // Check that the dimensions are sane IntSize size(mWidth, mHeight); if (size.width <= gfxPrefs::MaxCanvasSize() && size.height <= gfxPrefs::MaxCanvasSize() && size.width >= 0 && size.height >= 0) { SurfaceFormat format = GetSurfaceFormat(); nsIDocument* ownerDoc = nullptr; if (mCanvasElement) { @@ -1404,16 +1407,17 @@ CanvasRenderingContext2D::EnsureTarget(R #if USE_SKIA_GPU SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue(); if (glue && glue->GetGrContext() && glue->GetGLContext()) { mTarget = Factory::CreateDrawTargetSkiaWithGrContext(glue->GetGrContext(), size, format); if (mTarget) { AddDemotableContext(this); mBufferProvider = new PersistentBufferProviderBasic(mTarget); + mIsSkiaGL = true; } else { printf_stderr("Failed to create a SkiaGL DrawTarget, falling back to software\n"); mode = RenderingMode::SoftwareBackendMode; } } #endif } @@ -5555,19 +5559,21 @@ CanvasRenderingContext2D::GetBufferProvi return mBufferProvider; } already_AddRefed<CanvasLayer> CanvasRenderingContext2D::GetCanvasLayer(nsDisplayListBuilder* aBuilder, CanvasLayer *aOldLayer, LayerManager *aManager) { - if (mOpaque) { + if (mOpaque || mIsSkiaGL) { // If we're opaque then make sure we have a surface so we paint black // instead of transparent. + // If we're using SkiaGL, then SkiaGLTex() below needs the target to + // be accessible. EnsureTarget(); } // Don't call EnsureTarget() ... if there isn't already a surface, then // we have nothing to paint and there is no need to create a surface just // to paint nothing. Also, EnsureTarget() can cause creation of a persistent // layer manager which must NOT happen during a paint. if ((!mBufferProvider && !mTarget) || !IsTargetValid()) {
--- a/dom/canvas/CanvasRenderingContext2D.h +++ b/dom/canvas/CanvasRenderingContext2D.h @@ -713,16 +713,19 @@ protected: bool mOpaque; // This is true when the next time our layer is retrieved we need to // recreate it (i.e. our backing surface changed) bool mResetLayer; // This is needed for drawing in drawAsyncXULElement bool mIPC; + // True if the current DrawTarget is using skia-gl, used so we can avoid + // requesting the DT from mBufferProvider to check. + bool mIsSkiaGL; nsTArray<CanvasRenderingContext2DUserData*> mUserDatas; // If mCanvasElement is not provided, then a docshell is nsCOMPtr<nsIDocShell> mDocShell; // This is created lazily so it is necessary to call EnsureTarget before // accessing it. In the event of an error it will be equal to
--- a/dom/canvas/test/reftest/reftest.list +++ b/dom/canvas/test/reftest/reftest.list @@ -154,9 +154,9 @@ skip-if(!winWidget) pref(webgl.disable-a # focus rings pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(Android&&AndroidVersion<15,8,500) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html # Check that captureStream() displays in a local video element skip-if(winWidget&&layersGPUAccelerated&&d2d) == capturestream.html wrapper.html?green.png -fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html +fuzzy-if(azureSkiaGL,1,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
--- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -109,26 +109,28 @@ #include "nsFrameMessageManager.h" #include "nsGeolocationSettings.h" #include "nsHashPropertyBag.h" #include "nsIAlertsService.h" #include "nsIAppsService.h" #include "nsIClipboard.h" #include "nsContentPermissionHelper.h" #include "nsICycleCollectorListener.h" +#include "nsIDocShellTreeOwner.h" #include "nsIDocument.h" #include "nsIDOMGeoGeolocation.h" #include "nsIDOMGeoPositionError.h" #include "nsIDragService.h" #include "mozilla/dom/WakeLock.h" #include "nsIDOMWindow.h" #include "nsIExternalProtocolService.h" #include "nsIFormProcessor.h" #include "nsIGfxInfo.h" #include "nsIIdleService.h" +#include "nsIInterfaceRequestorUtils.h" #include "nsIMemoryInfoDumper.h" #include "nsIMemoryReporter.h" #include "nsIMozBrowserFrame.h" #include "nsIMutable.h" #include "nsIObserverService.h" #include "nsIPresShell.h" #include "nsIScriptError.h" #include "nsIScriptSecurityManager.h" @@ -1229,17 +1231,28 @@ ContentParent::CreateBrowserOrApp(const return nullptr; } } tabId = AllocateTabId(openerTabId, aContext.AsIPCTabContext(), constructorSender->ChildID()); } if (constructorSender) { + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + docShell->GetTreeOwner(getter_AddRefs(treeOwner)); + if (!treeOwner) { + return nullptr; + } + + nsCOMPtr<nsIWebBrowserChrome> wbc = do_GetInterface(treeOwner); + if (!wbc) { + return nullptr; + } uint32_t chromeFlags = 0; + wbc->GetChromeFlags(&chromeFlags); nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell); if (loadContext && loadContext->UsePrivateBrowsing()) { chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW; } bool affectLifetime; docShell->GetAffectPrivateSessionLifetime(&affectLifetime); if (affectLifetime) { @@ -2184,17 +2197,17 @@ ContentParent::NotifyTabDestroying(const void ContentParent::StartForceKillTimer() { if (mForceKillTimer || !mIPCOpen) { return; } - int32_t timeoutSecs = Preferences::GetInt("dom.ipc.tabs.shutdownTimeoutSecs", 0); + int32_t timeoutSecs = Preferences::GetInt("dom.ipc.tabs.shutdownTimeoutSecs", 5); if (timeoutSecs > 0) { mForceKillTimer = do_CreateInstance("@mozilla.org/timer;1"); MOZ_ASSERT(mForceKillTimer); mForceKillTimer->InitWithFuncCallback(ContentParent::ForceKillTimerCallback, this, timeoutSecs * 1000, nsITimer::TYPE_ONE_SHOT); } @@ -3466,16 +3479,24 @@ ContentParent::DeallocPRemoteSpellcheckE { delete parent; return true; } /* static */ void ContentParent::ForceKillTimerCallback(nsITimer* aTimer, void* aClosure) { +#ifdef ENABLE_TESTS + // We don't want to time out the content process during XPCShell tests. This + // is the easiest way to ensure that. + if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { + return; + } +#endif + auto self = static_cast<ContentParent*>(aClosure); self->KillHard("ShutDownKill"); } void ContentParent::KillHard(const char* aReason) { // On Windows, calling KillHard multiple times causes problems - the
--- a/dom/media/systemservices/LoadManager.cpp +++ b/dom/media/systemservices/LoadManager.cpp @@ -10,16 +10,18 @@ #include "prtime.h" #include "prinrval.h" #include "prsystem.h" #include "nsString.h" #include "nsThreadUtils.h" #include "nsReadableUtils.h" #include "nsIObserverService.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ArrayUtils.h" // NSPR_LOG_MODULES=LoadManager:5 PRLogModuleInfo *gLoadManagerLog = nullptr; #undef LOG #undef LOG_ENABLED #define LOG(args) MOZ_LOG(gLoadManagerLog, mozilla::LogLevel::Debug, args) #define LOG_ENABLED() MOZ_LOG_TEST(gLoadManagerLog, mozilla::LogLevel::Verbose) @@ -48,16 +50,21 @@ LoadManagerSingleton::LoadManagerSinglet gLoadManagerLog = PR_NewLogModule("LoadManager"); LOG(("LoadManager - Initializing (%dms x %d, %f, %f)", mLoadMeasurementInterval, mAveragingMeasurements, mHighLoadThreshold, mLowLoadThreshold)); MOZ_ASSERT(mHighLoadThreshold > mLowLoadThreshold); mLoadMonitor = new LoadMonitor(mLoadMeasurementInterval); mLoadMonitor->Init(mLoadMonitor); mLoadMonitor->SetLoadChangeCallback(this); + + mLastStateChange = TimeStamp::Now(); + for (auto &in_state : mTimeInState) { + in_state = 0; + } } LoadManagerSingleton::~LoadManagerSingleton() { LOG(("LoadManager: shutting down LoadMonitor")); MOZ_ASSERT(!mLoadMonitor, "why wasn't the LoadMonitor shut down in xpcom-shutdown?"); if (mLoadMonitor) { mLoadMonitor->Shutdown(); @@ -95,92 +102,130 @@ LoadManagerSingleton::LoadChanged(float MutexAutoLock lock(mLock); // Update total load, and total amount of measured seconds. mLoadSum += aSystemLoad; mLoadSumMeasurements++; if (mLoadSumMeasurements >= mAveragingMeasurements) { double averagedLoad = mLoadSum / (float)mLoadSumMeasurements; - webrtc::CPULoadState oldState = mCurrentState; + webrtc::CPULoadState newState = mCurrentState; if (mOveruseActive || averagedLoad > mHighLoadThreshold) { LOG(("LoadManager - LoadStressed")); - mCurrentState = webrtc::kLoadStressed; + newState = webrtc::kLoadStressed; } else if (averagedLoad < mLowLoadThreshold) { LOG(("LoadManager - LoadRelaxed")); - mCurrentState = webrtc::kLoadRelaxed; + newState = webrtc::kLoadRelaxed; } else { LOG(("LoadManager - LoadNormal")); - mCurrentState = webrtc::kLoadNormal; + newState = webrtc::kLoadNormal; } - if (oldState != mCurrentState) - LoadHasChanged(); + if (newState != mCurrentState) { + LoadHasChanged(newState); + } mLoadSum = 0; mLoadSumMeasurements = 0; } } void LoadManagerSingleton::OveruseDetected() { LOG(("LoadManager - Overuse Detected")); MutexAutoLock lock(mLock); mOveruseActive = true; if (mCurrentState != webrtc::kLoadStressed) { - mCurrentState = webrtc::kLoadStressed; - LoadHasChanged(); + LoadHasChanged(webrtc::kLoadStressed); } } void LoadManagerSingleton::NormalUsage() { LOG(("LoadManager - Overuse finished")); MutexAutoLock lock(mLock); mOveruseActive = false; } void -LoadManagerSingleton::LoadHasChanged() +LoadManagerSingleton::LoadHasChanged(webrtc::CPULoadState aNewState) { mLock.AssertCurrentThreadOwns(); - LOG(("LoadManager - Signaling LoadHasChanged to %d listeners", mObservers.Length())); + LOG(("LoadManager - Signaling LoadHasChanged from %d to %d to %d listeners", + mCurrentState, aNewState, mObservers.Length())); + + // Record how long we spent in this state for later Telemetry or display + TimeStamp now = TimeStamp::Now(); + mTimeInState[mCurrentState] += (now - mLastStateChange).ToMilliseconds(); + mLastStateChange = now; + + mCurrentState = aNewState; for (size_t i = 0; i < mObservers.Length(); i++) { mObservers.ElementAt(i)->onLoadStateChanged(mCurrentState); } } void LoadManagerSingleton::AddObserver(webrtc::CPULoadStateObserver * aObserver) { LOG(("LoadManager - Adding Observer")); MutexAutoLock lock(mLock); mObservers.AppendElement(aObserver); if (mObservers.Length() == 1) { if (!mLoadMonitor) { mLoadMonitor = new LoadMonitor(mLoadMeasurementInterval); mLoadMonitor->Init(mLoadMonitor); mLoadMonitor->SetLoadChangeCallback(this); + mLastStateChange = TimeStamp::Now(); } } } void LoadManagerSingleton::RemoveObserver(webrtc::CPULoadStateObserver * aObserver) { LOG(("LoadManager - Removing Observer")); MutexAutoLock lock(mLock); if (!mObservers.RemoveElement(aObserver)) { LOG(("LoadManager - Element to remove not found")); } if (mObservers.Length() == 0) { if (mLoadMonitor) { + // Record how long we spent in the final state for later Telemetry or display + TimeStamp now = TimeStamp::Now(); + mTimeInState[mCurrentState] += (now - mLastStateChange).ToMilliseconds(); + + float total = 0; + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(mTimeInState); i++) { + total += mTimeInState[i]; + } + // Don't include short calls; we don't have reasonable load data, and + // such short calls rarely reach a stable state. Keep relatively + // short calls separate from longer ones + bool log = total > 5*PR_MSEC_PER_SEC; + bool small = log && total < 30*PR_MSEC_PER_SEC; + if (log) { + // Note: We don't care about rounding here; thus total may be < 100 + Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_RELAXED_SHORT : + Telemetry::WEBRTC_LOAD_STATE_RELAXED, + (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadRelaxed]/total * 100)); + Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_NORMAL_SHORT : + Telemetry::WEBRTC_LOAD_STATE_NORMAL, + (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadNormal]/total * 100)); + Telemetry::Accumulate(small ? Telemetry::WEBRTC_LOAD_STATE_STRESSED_SHORT : + Telemetry::WEBRTC_LOAD_STATE_STRESSED, + (uint32_t) (mTimeInState[webrtc::CPULoadState::kLoadStressed]/total * 100)); + } + for (auto &in_state : mTimeInState) { + in_state = 0; + } + // Dance to avoid deadlock on mLock! nsRefPtr<LoadMonitor> loadMonitor = mLoadMonitor.forget(); MutexAutoUnlock unlock(mLock); loadMonitor->Shutdown(); } } }
--- a/dom/media/systemservices/LoadManager.h +++ b/dom/media/systemservices/LoadManager.h @@ -45,25 +45,28 @@ public: private: LoadManagerSingleton(int aLoadMeasurementInterval, int aAveragingMeasurements, float aHighLoadThreshold, float aLowLoadThreshold); ~LoadManagerSingleton(); - void LoadHasChanged(); + void LoadHasChanged(webrtc::CPULoadState aNewState); nsRefPtr<LoadMonitor> mLoadMonitor; // This protects access to the mObservers list, the current state, and // pretty much all the other members (below). Mutex mLock; nsTArray<webrtc::CPULoadStateObserver*> mObservers; webrtc::CPULoadState mCurrentState; + TimeStamp mLastStateChange; + float mTimeInState[static_cast<int>(webrtc::kLoadLast)]; + // Set when overuse was signaled to us, and hasn't been un-signaled yet. bool mOveruseActive; float mLoadSum; int mLoadSumMeasurements; // Load measurement settings int mLoadMeasurementInterval; int mAveragingMeasurements; float mHighLoadThreshold;
--- a/dom/presentation/Presentation.cpp +++ b/dom/presentation/Presentation.cpp @@ -1,130 +1,81 @@ /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim:set ts=2 sw=2 sts=2 et cindent: */ /* 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 "mozilla/AsyncEventDispatcher.h" #include "mozilla/dom/PresentationBinding.h" #include "mozilla/dom/Promise.h" #include "nsCycleCollectionParticipant.h" -#include "nsIPresentationDeviceManager.h" #include "nsIPresentationService.h" -#include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" #include "Presentation.h" -#include "PresentationCallbacks.h" -#include "PresentationSession.h" +#include "PresentationReceiver.h" using namespace mozilla; using namespace mozilla::dom; -NS_IMPL_CYCLE_COLLECTION_CLASS(Presentation) - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Presentation, DOMEventTargetHelper) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultRequest) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessions) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingGetSessionPromises) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Presentation, DOMEventTargetHelper) - tmp->Shutdown(); - NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultRequest) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessions) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingGetSessionPromises) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_INHERITED(Presentation, DOMEventTargetHelper, + mDefaultRequest, mReceiver) NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation) - NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) /* static */ already_AddRefed<Presentation> Presentation::Create(nsPIDOMWindow* aWindow) { nsRefPtr<Presentation> presentation = new Presentation(aWindow); return NS_WARN_IF(!presentation->Init()) ? nullptr : presentation.forget(); } Presentation::Presentation(nsPIDOMWindow* aWindow) : DOMEventTargetHelper(aWindow) { } Presentation::~Presentation() { - Shutdown(); } bool Presentation::Init() { nsCOMPtr<nsIPresentationService> service = do_GetService(PRESENTATION_SERVICE_CONTRACTID); if (NS_WARN_IF(!service)) { return false; } if (NS_WARN_IF(!GetOwner())) { return false; } - mWindowId = GetOwner()->WindowID(); - // Check if a session instance is required now. A session may already be - // connecting before the web content gets loaded in a presenting browsing - // context (receiver). + // Check if a receiver instance is required now. A session may already be + // connecting before the web content gets loaded in a receiving browsing + // context. nsAutoString sessionId; - nsresult rv = service->GetExistentSessionIdAtLaunch(mWindowId, sessionId); + nsresult rv = service->GetExistentSessionIdAtLaunch(GetOwner()->WindowID(), sessionId); if (NS_WARN_IF(NS_FAILED(rv))) { return false; } if (!sessionId.IsEmpty()) { - rv = NotifySessionConnect(mWindowId, sessionId); - if (NS_WARN_IF(NS_FAILED(rv))) { + mReceiver = PresentationReceiver::Create(GetOwner(), sessionId); + if (NS_WARN_IF(!mReceiver)) { return false; } } - // Register listener for incoming sessions. - rv = service->RegisterRespondingListener(mWindowId, this); - if (NS_WARN_IF(NS_FAILED(rv))) { - return false; - } - return true; } -void Presentation::Shutdown() -{ - mDefaultRequest = nullptr; - mSessions.Clear(); - mPendingGetSessionPromises.Clear(); - - // Unregister listener for incoming sessions. - nsCOMPtr<nsIPresentationService> service = - do_GetService(PRESENTATION_SERVICE_CONTRACTID); - if (NS_WARN_IF(!service)) { - return; - } - - nsresult rv = service->UnregisterRespondingListener(mWindowId); - NS_WARN_IF(NS_FAILED(rv)); -} - -/* virtual */ void -Presentation::DisconnectFromOwner() -{ - Shutdown(); - DOMEventTargetHelper::DisconnectFromOwner(); -} - /* virtual */ JSObject* Presentation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { return PresentationBinding::Wrap(aCx, this, aGivenProto); } void @@ -135,86 +86,14 @@ Presentation::SetDefaultRequest(Presenta already_AddRefed<PresentationRequest> Presentation::GetDefaultRequest() const { nsRefPtr<PresentationRequest> request = mDefaultRequest; return request.forget(); } -already_AddRefed<Promise> -Presentation::GetSession(ErrorResult& aRv) -{ - nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); - if (NS_WARN_IF(!global)) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; - } - - nsRefPtr<Promise> promise = Promise::Create(global, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - // If there's no existing session, leave the promise pending until a - // connecting request arrives from the controlling browsing context (sender). - // http://w3c.github.io/presentation-api/#dom-presentation-getsession - if (!mSessions.IsEmpty()) { - promise->MaybeResolve(mSessions[0]); - } else { - mPendingGetSessionPromises.AppendElement(promise); - } - - return promise.forget(); -} - -already_AddRefed<Promise> -Presentation::GetSessions(ErrorResult& aRv) const +already_AddRefed<PresentationReceiver> +Presentation::GetReceiver() const { - nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); - if (NS_WARN_IF(!global)) { - aRv.Throw(NS_ERROR_UNEXPECTED); - return nullptr; - } - - nsRefPtr<Promise> promise = Promise::Create(global, aRv); - if (NS_WARN_IF(aRv.Failed())) { - return nullptr; - } - - promise->MaybeResolve(mSessions); - return promise.forget(); + nsRefPtr<PresentationReceiver> receiver = mReceiver; + return receiver.forget(); } - -NS_IMETHODIMP -Presentation::NotifySessionConnect(uint64_t aWindowId, - const nsAString& aSessionId) -{ - if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) { - return NS_ERROR_INVALID_ARG; - } - - nsRefPtr<PresentationSession> session = - PresentationSession::Create(GetOwner(), aSessionId, - PresentationSessionState::Disconnected); - if (NS_WARN_IF(!session)) { - return NS_ERROR_NOT_AVAILABLE; - } - mSessions.AppendElement(session); - - // Resolve pending |GetSession| promises if any. - if (!mPendingGetSessionPromises.IsEmpty()) { - for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) { - mPendingGetSessionPromises[i]->MaybeResolve(session); - } - mPendingGetSessionPromises.Clear(); - } - - return DispatchSessionAvailableEvent(); -} - -nsresult -Presentation::DispatchSessionAvailableEvent() -{ - nsRefPtr<AsyncEventDispatcher> asyncDispatcher = - new AsyncEventDispatcher(this, NS_LITERAL_STRING("sessionavailable"), false); - return asyncDispatcher->PostDOMEvent(); -}
--- a/dom/presentation/Presentation.h +++ b/dom/presentation/Presentation.h @@ -3,68 +3,50 @@ /* 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_Presentation_h #define mozilla_dom_Presentation_h #include "mozilla/DOMEventTargetHelper.h" -#include "nsIPresentationListener.h" namespace mozilla { namespace dom { class Promise; +class PresentationReceiver; class PresentationRequest; -class PresentationSession; class Presentation final : public DOMEventTargetHelper - , public nsIPresentationRespondingListener { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation, DOMEventTargetHelper) - NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER static already_AddRefed<Presentation> Create(nsPIDOMWindow* aWindow); - virtual void DisconnectFromOwner() override; - virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; // WebIDL (public APIs) void SetDefaultRequest(PresentationRequest* aRequest); already_AddRefed<PresentationRequest> GetDefaultRequest() const; - already_AddRefed<Promise> GetSession(ErrorResult& aRv); - - already_AddRefed<Promise> GetSessions(ErrorResult& aRv) const; - - IMPL_EVENT_HANDLER(sessionavailable); + already_AddRefed<PresentationReceiver> GetReceiver() const; private: explicit Presentation(nsPIDOMWindow* aWindow); ~Presentation(); bool Init(); - void Shutdown(); - - nsresult DispatchSessionAvailableEvent(); - - // Store the inner window ID for |UnregisterRespondingListener| call in - // |Shutdown| since the inner window may not exist at that moment. - uint64_t mWindowId; - nsRefPtr<PresentationRequest> mDefaultRequest; - nsTArray<nsRefPtr<PresentationSession>> mSessions; - nsTArray<nsRefPtr<Promise>> mPendingGetSessionPromises; + nsRefPtr<PresentationReceiver> mReceiver; }; } // namespace dom } // namespace mozilla #endif // mozilla_dom_Presentation_h
new file mode 100644 --- /dev/null +++ b/dom/presentation/PresentationReceiver.cpp @@ -0,0 +1,194 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "mozilla/AsyncEventDispatcher.h" +#include "mozilla/dom/PresentationReceiverBinding.h" +#include "mozilla/dom/Promise.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIPresentationService.h" +#include "nsServiceManagerUtils.h" +#include "PresentationReceiver.h" +#include "PresentationSession.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationReceiver) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationReceiver, DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessions) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingGetSessionPromises) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationReceiver, DOMEventTargetHelper) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessions) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingGetSessionPromises) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(PresentationReceiver, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(PresentationReceiver, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationReceiver) + NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +/* static */ already_AddRefed<PresentationReceiver> +PresentationReceiver::Create(nsPIDOMWindow* aWindow, + const nsAString& aSessionId) +{ + nsRefPtr<PresentationReceiver> receiver = new PresentationReceiver(aWindow); + return NS_WARN_IF(!receiver->Init(aSessionId)) ? nullptr : receiver.forget(); +} + +PresentationReceiver::PresentationReceiver(nsPIDOMWindow* aWindow) + : DOMEventTargetHelper(aWindow) +{ +} + +PresentationReceiver::~PresentationReceiver() +{ + Shutdown(); +} + +bool +PresentationReceiver::Init(const nsAString& aSessionId) +{ + if (NS_WARN_IF(!GetOwner())) { + return false; + } + mWindowId = GetOwner()->WindowID(); + + if (!aSessionId.IsEmpty()) { + nsresult rv = NotifySessionConnect(mWindowId, aSessionId); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + } + + // Register listener for incoming sessions. + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return false; + } + + nsresult rv = service->RegisterRespondingListener(mWindowId, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + return true; +} + +void PresentationReceiver::Shutdown() +{ + mSessions.Clear(); + mPendingGetSessionPromises.Clear(); + + // Unregister listener for incoming sessions. + nsCOMPtr<nsIPresentationService> service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return; + } + + nsresult rv = service->UnregisterRespondingListener(mWindowId); + NS_WARN_IF(NS_FAILED(rv)); +} + +/* virtual */ void +PresentationReceiver::DisconnectFromOwner() +{ + Shutdown(); + DOMEventTargetHelper::DisconnectFromOwner(); +} + +/* virtual */ JSObject* +PresentationReceiver::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) +{ + return PresentationReceiverBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed<Promise> +PresentationReceiver::GetSession(ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsRefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // If there's no existing session, leave the promise pending until a + // connecting request arrives from the controlling browsing context (sender). + // http://w3c.github.io/presentation-api/#dom-presentation-getsession + if (!mSessions.IsEmpty()) { + promise->MaybeResolve(mSessions[0]); + } else { + mPendingGetSessionPromises.AppendElement(promise); + } + + return promise.forget(); +} + +already_AddRefed<Promise> +PresentationReceiver::GetSessions(ErrorResult& aRv) const +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsRefPtr<Promise> promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + promise->MaybeResolve(mSessions); + return promise.forget(); +} + +NS_IMETHODIMP +PresentationReceiver::NotifySessionConnect(uint64_t aWindowId, + const nsAString& aSessionId) +{ + if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) { + return NS_ERROR_INVALID_ARG; + } + + nsRefPtr<PresentationSession> session = + PresentationSession::Create(GetOwner(), aSessionId, + PresentationSessionState::Disconnected); + if (NS_WARN_IF(!session)) { + return NS_ERROR_NOT_AVAILABLE; + } + mSessions.AppendElement(session); + + // Resolve pending |GetSession| promises if any. + if (!mPendingGetSessionPromises.IsEmpty()) { + for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) { + mPendingGetSessionPromises[i]->MaybeResolve(session); + } + mPendingGetSessionPromises.Clear(); + } + + return DispatchSessionAvailableEvent(); +} + +nsresult +PresentationReceiver::DispatchSessionAvailableEvent() +{ + nsRefPtr<AsyncEventDispatcher> asyncDispatcher = + new AsyncEventDispatcher(this, NS_LITERAL_STRING("sessionavailable"), false); + return asyncDispatcher->PostDOMEvent(); +}
new file mode 100644 --- /dev/null +++ b/dom/presentation/PresentationReceiver.h @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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_PresentationReceiver_h +#define mozilla_dom_PresentationReceiver_h + +#include "mozilla/DOMEventTargetHelper.h" +#include "nsIPresentationListener.h" + +namespace mozilla { +namespace dom { + +class Promise; +class PresentationSession; + +class PresentationReceiver final : public DOMEventTargetHelper + , public nsIPresentationRespondingListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationReceiver, + DOMEventTargetHelper) + NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER + + static already_AddRefed<PresentationReceiver> Create(nsPIDOMWindow* aWindow, + const nsAString& aSessionId); + + virtual void DisconnectFromOwner() override; + + virtual JSObject* WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) override; + + // WebIDL (public APIs) + already_AddRefed<Promise> GetSession(ErrorResult& aRv); + + already_AddRefed<Promise> GetSessions(ErrorResult& aRv) const; + + IMPL_EVENT_HANDLER(sessionavailable); + +private: + explicit PresentationReceiver(nsPIDOMWindow* aWindow); + + ~PresentationReceiver(); + + bool Init(const nsAString& aSessionId); + + void Shutdown(); + + nsresult DispatchSessionAvailableEvent(); + + // Store the inner window ID for |UnregisterRespondingListener| call in + // |Shutdown| since the inner window may not exist at that moment. + uint64_t mWindowId; + + nsTArray<nsRefPtr<PresentationSession>> mSessions; + nsTArray<nsRefPtr<Promise>> mPendingGetSessionPromises; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationReceiver_h
--- a/dom/presentation/ipc/PresentationParent.cpp +++ b/dom/presentation/ipc/PresentationParent.cpp @@ -45,16 +45,21 @@ PresentationParent::ActorDestroy(ActorDe { mActorDestroyed = true; for (uint32_t i = 0; i < mSessionIds.Length(); i++) { NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(mSessionIds[i]))); } mSessionIds.Clear(); + for (uint32_t i = 0; i < mWindowIds.Length(); i++) { + NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(mWindowIds[i]))); + } + mWindowIds.Clear(); + mService->UnregisterAvailabilityListener(this); mService = nullptr; } bool PresentationParent::RecvPPresentationRequestConstructor( PPresentationRequestParent* aActor, const PresentationIPCRequest& aRequest) @@ -144,24 +149,27 @@ PresentationParent::RecvUnregisterSessio NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(aSessionId))); return true; } /* virtual */ bool PresentationParent::RecvRegisterRespondingHandler(const uint64_t& aWindowId) { MOZ_ASSERT(mService); + + mWindowIds.AppendElement(aWindowId); NS_WARN_IF(NS_FAILED(mService->RegisterRespondingListener(aWindowId, this))); return true; } /* virtual */ bool PresentationParent::RecvUnregisterRespondingHandler(const uint64_t& aWindowId) { MOZ_ASSERT(mService); + mWindowIds.RemoveElement(aWindowId); NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(aWindowId))); return true; } NS_IMETHODIMP PresentationParent::NotifyAvailableChange(bool aAvailable) { if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) {
--- a/dom/presentation/ipc/PresentationParent.h +++ b/dom/presentation/ipc/PresentationParent.h @@ -59,16 +59,17 @@ public: virtual bool RecvNotifyReceiverReady(const nsString& aSessionId) override; private: virtual ~PresentationParent(); bool mActorDestroyed; nsCOMPtr<nsIPresentationService> mService; nsTArray<nsString> mSessionIds; + nsTArray<uint64_t> mWindowIds; }; class PresentationRequestParent final : public PPresentationRequestParent , public nsIPresentationServiceCallback { friend class PresentationParent; public:
--- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -12,31 +12,33 @@ MOCHITEST_MANIFESTS += ['tests/mochitest EXPORTS.mozilla.dom += [ 'ipc/PresentationChild.h', 'ipc/PresentationIPCService.h', 'ipc/PresentationParent.h', 'Presentation.h', 'PresentationAvailability.h', 'PresentationCallbacks.h', 'PresentationDeviceManager.h', + 'PresentationReceiver.h', 'PresentationRequest.h', 'PresentationService.h', 'PresentationSession.h', 'PresentationSessionInfo.h', 'PresentationSessionTransport.h', ] UNIFIED_SOURCES += [ 'ipc/PresentationChild.cpp', 'ipc/PresentationIPCService.cpp', 'ipc/PresentationParent.cpp', 'Presentation.cpp', 'PresentationAvailability.cpp', 'PresentationCallbacks.cpp', 'PresentationDeviceManager.cpp', + 'PresentationReceiver.cpp', 'PresentationRequest.cpp', 'PresentationService.cpp', 'PresentationSession.cpp', 'PresentationSessionInfo.cpp', 'PresentationSessionRequest.cpp', 'PresentationSessionTransport.cpp', ]
--- a/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html +++ b/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html @@ -24,28 +24,18 @@ function info(msg) { function finish() { alert('DONE'); } function testSessionAvailable() { return new Promise(function(aResolve, aReject) { ok(navigator.presentation, "navigator.presentation should be available in OOP pages."); - - navigator.presentation.getSessions().then( - function(aSessions) { - is(aSessions.length, 0, "Non-receiving OOP pages shouldn't get a predefined presentation session instance."); - aResolve(); - }, - function(aError) { - ok(false, "Error occurred when getting sessions: " + aError); - teardown(); - aReject(); - } - ); + ok(!navigator.presentation.receiver, "Non-receiving OOP pages shouldn't get a presentation receiver instance."); + aResolve(); }); } testSessionAvailable(). then(finish); </script> </body>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver.html +++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html @@ -30,18 +30,19 @@ function finish() { window.parent.postMessage('DONE', '*'); } var session; function testSessionAvailable() { return new Promise(function(aResolve, aReject) { ok(navigator.presentation, "navigator.presentation should be available."); + ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available."); - navigator.presentation.getSession().then( + navigator.presentation.receiver.getSession().then( function(aSession) { session = aSession; ok(session.id, "Session ID should be set: " + session.id); is(session.state, "disconnected", "Session state at receiver side should be disconnected by default."); aResolve(); }, function(aError) {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html +++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html @@ -30,18 +30,19 @@ function finish() { alert('DONE'); } var session; function testSessionAvailable() { return new Promise(function(aResolve, aReject) { ok(navigator.presentation, "navigator.presentation should be available."); + ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available."); - navigator.presentation.getSession().then( + navigator.presentation.receiver.getSession().then( function(aSession) { session = aSession; ok(session.id, "Session ID should be set: " + session.id); is(session.state, "disconnected", "Session state at receiver side should be disconnected by default."); aResolve(); }, function(aError) {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html +++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html @@ -30,18 +30,19 @@ function finish() { window.parent.postMessage('DONE', '*'); } var session; function testSessionAvailable() { return new Promise(function(aResolve, aReject) { ok(navigator.presentation, "navigator.presentation should be available."); + ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available."); - navigator.presentation.getSession().then( + navigator.presentation.receiver.getSession().then( function(aSession) { session = aSession; ok(session.id, "Session ID should be set: " + session.id); is(session.state, "disconnected", "Session state at receiver side should be disconnected by default."); aResolve(); }, function(aError) {
--- a/dom/presentation/tests/mochitest/test_presentation_receiver.html +++ b/dom/presentation/tests/mochitest/test_presentation_receiver.html @@ -88,28 +88,18 @@ function setup() { function testIncomingSessionRequest() { return new Promise(function(aResolve, aReject) { gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) { gScript.removeMessageListener('receiver-launching', launchReceiverHandler); info("Trying to launch receiver page."); ok(navigator.presentation, "navigator.presentation should be available in in-process pages."); - - navigator.presentation.getSessions().then( - function(aSessions) { - is(aSessions.length, 0, "Non-receiving in-process pages shouldn't get a predefined presentation session instance."); - aResolve(); - }, - function(aError) { - ok(false, "Error occurred when getting sessions: " + aError); - teardown(); - aReject(); - } - ); + ok(!navigator.presentation.receiver, "Non-receiving in-process pages shouldn't get a presentation receiver instance."); + aResolve(); }); gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl); }); } function teardown() { gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
--- a/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html +++ b/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html @@ -76,16 +76,18 @@ function testStartSession() { }); gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() { gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler); info("Data transport channel is initialized."); gScript.sendAsyncMessage('trigger-incoming-answer'); }); + ok(!navigator.presentation.receiver, "Sender shouldn't get a presentation receiver instance."); + navigator.presentation.defaultRequest.onsessionconnect = function(aEvent) { navigator.presentation.defaultRequest.onsessionconnect = null; session = aEvent.session; ok(session, "|sessionconnect| event is fired with a session."); ok(session.id, "Session ID should be set."); is(session.state, "connected", "Session state at sender side should be connected by default."); aResolve(); };
--- a/dom/tests/mochitest/localstorage/chrome.ini +++ b/dom/tests/mochitest/localstorage/chrome.ini @@ -2,10 +2,11 @@ skip-if = buildapp == 'b2g' || os == 'android' support-files = frame_clear_browser_data.html page_blank.html [test_app_uninstall.html] [test_clear_browser_data.html] [test_localStorageBasePrivateBrowsing_perwindowpb.html] +skip-if = true # bug 1156725 [test_localStorageFromChrome.xhtml] [test_localStorageQuotaPrivateBrowsing_perwindowpb.html]
--- a/dom/webidl/Presentation.webidl +++ b/dom/webidl/Presentation.webidl @@ -13,31 +13,14 @@ interface Presentation : EventTarget { * controller's behalf, it MUST start a presentation session using the default * presentation request (as if the controller had called |defaultRequest.start()|). * * Only used by controlling browsing context (senders). */ attribute PresentationRequest? defaultRequest; /* - * Get the first connected presentation session in a presenting browsing - * context. - * - * Only used by presenting browsing context (receivers). + * This should be available on the receiving browsing context in order to + * access the controlling browsing context and communicate with them. */ - [Throws] - Promise<PresentationSession> getSession(); - - /* - * Get all connected presentation sessions in a presenting browsing context. - * - * Only used by presenting browsing context (receivers). - */ - [Throws] - Promise<sequence<PresentationSession>> getSessions(); - - /* - * It is called when an incoming session is connecting. - * - * Only used by presenting browsing context (receivers). - */ - attribute EventHandler onsessionavailable; + [SameObject] + readonly attribute PresentationReceiver? receiver; };
new file mode 100644 --- /dev/null +++ b/dom/webidl/PresentationReceiver.webidl @@ -0,0 +1,27 @@ +/* -*- 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/. + */ + +[Pref="dom.presentation.enabled", + CheckAnyPermissions="presentation"] +interface PresentationReceiver : EventTarget { + /* + * Get the first connected presentation session in a receiving browsing + * context. + */ + [Throws] + Promise<PresentationSession> getSession(); + + /* + * Get all connected presentation sessions in a receiving browsing context. + */ + [Throws] + Promise<sequence<PresentationSession>> getSessions(); + + /* + * It is called when an incoming session is connecting. + */ + attribute EventHandler onsessionavailable; +};
--- a/dom/webidl/SharedWorker.webidl +++ b/dom/webidl/SharedWorker.webidl @@ -1,13 +1,12 @@ /* -*- 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/. */ -[Pref="dom.workers.sharedWorkers.enabled", - Constructor(DOMString scriptURL, optional DOMString name)] +[Constructor(DOMString scriptURL, optional DOMString name)] interface SharedWorker : EventTarget { readonly attribute MessagePort port; }; SharedWorker implements AbstractWorker;
--- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -368,16 +368,17 @@ WEBIDL_FILES = [ 'PluginArray.webidl', 'PointerEvent.webidl', 'PopupBoxObject.webidl', 'Position.webidl', 'PositionError.webidl', 'Presentation.webidl', 'PresentationAvailability.webidl', 'PresentationDeviceInfoManager.webidl', + 'PresentationReceiver.webidl', 'PresentationRequest.webidl', 'PresentationSession.webidl', 'ProcessingInstruction.webidl', 'ProfileTimelineMarker.webidl', 'Promise.webidl', 'PromiseDebugging.webidl', 'RadioNodeList.webidl', 'Range.webidl',
--- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -3371,17 +3371,16 @@ ServiceWorkerManager::FindScopeForPath(c aMatch = current; return true; } } return false; } -#ifdef DEBUG /* static */ bool ServiceWorkerManager::HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope) { nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); nsAutoCString scopeKey; @@ -3392,17 +3391,16 @@ ServiceWorkerManager::HasScope(nsIPrinci RegistrationDataPerPrincipal* data; if (!swm->mRegistrationInfos.Get(scopeKey, &data)) { return false; } return data->mOrderedScopes.Contains(aScope); } -#endif /* static */ void ServiceWorkerManager::RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration) { nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); MOZ_ASSERT(swm); nsAutoCString scopeKey; @@ -4631,17 +4629,18 @@ ServiceWorkerManager::CreateNewRegistrat return registration; } void ServiceWorkerManager::MaybeRemoveRegistration(ServiceWorkerRegistrationInfo* aRegistration) { MOZ_ASSERT(aRegistration); nsRefPtr<ServiceWorkerInfo> newest = aRegistration->Newest(); - if (!newest) { + if (!newest && HasScope(aRegistration->mPrincipal, aRegistration->mScope)) { + aRegistration->Clear(); RemoveRegistration(aRegistration); } } void ServiceWorkerManager::RemoveRegistrationInternal(ServiceWorkerRegistrationInfo* aRegistration) { MOZ_ASSERT(aRegistration);
--- a/dom/workers/ServiceWorkerManager.h +++ b/dom/workers/ServiceWorkerManager.h @@ -515,20 +515,18 @@ private: AddScopeAndRegistration(const nsACString& aScope, ServiceWorkerRegistrationInfo* aRegistation); static bool FindScopeForPath(const nsACString& aScopeKey, const nsACString& aPath, RegistrationDataPerPrincipal** aData, nsACString& aMatch); -#ifdef DEBUG static bool HasScope(nsIPrincipal* aPrincipal, const nsACString& aScope); -#endif static void RemoveScopeAndRegistration(ServiceWorkerRegistrationInfo* aRegistration); void QueueFireEventOnServiceWorkerRegistrations(ServiceWorkerRegistrationInfo* aRegistration, const nsAString& aName);
--- a/dom/workers/test/test_bug1132395.html +++ b/dom/workers/test/test_bug1132395.html @@ -11,32 +11,30 @@ </head> <body> <script class="testbody" type="text/javascript"> // This test is full of dummy debug messages. This is because I need to follow // an hard-to-reproduce timeout failure. -SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() { - info("test started"); - var sw = new SharedWorker('bug1132395_sharedWorker.js'); - sw.port.onmessage = function(event) { - info("sw.onmessage received"); - ok(true, "We didn't crash."); - SimpleTest.finish(); - } +info("test started"); +var sw = new SharedWorker('bug1132395_sharedWorker.js'); +sw.port.onmessage = function(event) { + info("sw.onmessage received"); + ok(true, "We didn't crash."); + SimpleTest.finish(); +} - sw.onerror = function(event) { - ok(false, "Failed to create a ServiceWorker"); - SimpleTest.finish(); - } +sw.onerror = function(event) { + ok(false, "Failed to create a ServiceWorker"); + SimpleTest.finish(); +} - info("sw.postmessage called"); - sw.port.postMessage('go'); -}); +info("sw.postmessage called"); +sw.port.postMessage('go'); SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/dom/workers/test/test_bug949946.html +++ b/dom/workers/test/test_bug949946.html @@ -10,22 +10,17 @@ <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"></pre> <script class="testbody" type="text/javascript"> - SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() { - new SharedWorker('sharedWorker_sharedWorker.js'); - new SharedWorker('sharedWorker_sharedWorker.js', ':'); - new SharedWorker('sharedWorker_sharedWorker.js', '|||'); - ok(true, "3 SharedWorkers created!"); - SimpleTest.finish(); - }); - - SimpleTest.waitForExplicitFinish(); +new SharedWorker('sharedWorker_sharedWorker.js'); +new SharedWorker('sharedWorker_sharedWorker.js', ':'); +new SharedWorker('sharedWorker_sharedWorker.js', '|||'); +ok(true, "3 SharedWorkers created!"); </script> </pre> </body> </html>
--- a/dom/workers/test/test_consoleSharedWorkers.html +++ b/dom/workers/test/test_consoleSharedWorkers.html @@ -42,18 +42,15 @@ SpecialPowers.removeObserver(this, "console-api-log-event"); SimpleTest.finish(); return; } } } var cl = new consoleListener(); - - SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]] }, function() { - new SharedWorker('sharedWorker_console.js'); - }); + new SharedWorker('sharedWorker_console.js'); SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/workers/test/test_multi_sharedWorker.html +++ b/dom/workers/test/test_multi_sharedWorker.html @@ -6,18 +6,16 @@ <html> <head> <title>Test for SharedWorker</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> <script class="testbody" type="text/javascript;version=1.7"> "use strict"; - const swPref = "dom.workers.sharedWorkers.enabled"; - const basePath = location.pathname.substring(0, location.pathname.lastIndexOf("/") + 1); const baseURL = location.origin + basePath; const frameRelativeURL = "multi_sharedWorker_frame.html"; const frameAbsoluteURL = baseURL + frameRelativeURL; const workerAbsoluteURL = @@ -25,23 +23,16 @@ const storedData = "0123456789abcdefghijklmnopqrstuvwxyz"; const errorMessage = "Error: Expected"; const errorLineno = 34; let testGenerator = (function() { SimpleTest.waitForExplicitFinish(); - if (!SpecialPowers.getBoolPref(swPref)) { - ok(!("SharedWorker" in window), "No SharedWorker without pref set"); - } - - SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, sendToGenerator); - yield undefined; - window.addEventListener("message", function(event) { if (typeof(event.data) == "string") { info(event.data); } else { sendToGenerator(event); } });
--- a/dom/workers/test/test_multi_sharedWorker_lifetimes.html +++ b/dom/workers/test/test_multi_sharedWorker_lifetimes.html @@ -6,38 +6,32 @@ <html> <head> <title>Test for SharedWorker</title> <script src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"> <script class="testbody" type="text/javascript;version=1.7"> "use strict"; - const swPref = "dom.workers.sharedWorkers.enabled"; const scrollbarPref = "layout.testing.overlay-scrollbars.always-visible"; const bfCacheEnabledPref = "browser.sessionhistory.cache_subframes"; const bfCacheDepthPref = "browser.sessionhistory.max_total_viewers"; const bfCacheDepth = 10; const frameRelativeURL = "multi_sharedWorker_frame.html"; const storedData = "0123456789abcdefghijklmnopqrstuvwxyz"; let testGenerator = (function() { SimpleTest.waitForExplicitFinish(); - if (!SpecialPowers.getBoolPref(swPref)) { - ok(!("SharedWorker" in window), "No SharedWorker without pref set"); - } - - // Enable SharedWorkers and force scrollbar to always be shown. The - // scrollbar setting is necessary to avoid the fade-in/fade-out from - // evicting our document from the BF cache below. If bug 1049277 - // is fixed, then we can stop setting the scrollbar pref here. - SpecialPowers.pushPrefEnv({ set: [[swPref, true], - [scrollbarPref, true]] }, + // Force scrollbar to always be shown. The scrollbar setting is + // necessary to avoid the fade-in/fade-out from evicting our document + // from the BF cache below. If bug 1049277 is fixed, then we can + // stop setting the scrollbar pref here. + SpecialPowers.pushPrefEnv({ set: [[scrollbarPref, true]] }, sendToGenerator); yield undefined; window.addEventListener("message", function(event) { if (typeof(event.data) == "string") { info(event.data); } else { sendToGenerator(event);
--- a/dom/workers/test/test_sharedWorker.html +++ b/dom/workers/test/test_sharedWorker.html @@ -12,68 +12,60 @@ </head> <body> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> <script class="testbody"> "use strict"; - const swPref = "dom.workers.sharedWorkers.enabled"; - const href = window.location.href; const filename = "sharedWorker_sharedWorker.js"; const sentMessage = "ping"; const errorFilename = href.substring(0, href.lastIndexOf("/") + 1) + filename; const errorLine = 86; const errorColumn = 0; - if (!SpecialPowers.getBoolPref(swPref)) { - ok(!("SharedWorker" in window), "No SharedWorker without pref set"); - } - - SpecialPowers.pushPrefEnv({ set: [[swPref, true]] }, function() { - var worker = new SharedWorker(filename); + var worker = new SharedWorker(filename); - ok(worker instanceof SharedWorker, "Got SharedWorker instance"); - ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'"); - ok(worker.port instanceof MessagePort, - "Shared worker has MessagePort"); + ok(worker instanceof SharedWorker, "Got SharedWorker instance"); + ok(!("postMessage" in worker), "SharedWorker has no 'postMessage'"); + ok(worker.port instanceof MessagePort, + "Shared worker has MessagePort"); - var receivedMessage; - var receivedError; + var receivedMessage; + var receivedError; - worker.port.onmessage = function(event) { - ok(event instanceof MessageEvent, "Got a MessageEvent"); - ok(event.target === worker.port, - "MessageEvent has correct 'target' property"); - is(event.data, sentMessage, "Got correct message"); - ok(receivedMessage === undefined, "Haven't gotten message yet"); - receivedMessage = event.data; - if (receivedError) { - SimpleTest.finish(); - } - }; + worker.port.onmessage = function(event) { + ok(event instanceof MessageEvent, "Got a MessageEvent"); + ok(event.target === worker.port, + "MessageEvent has correct 'target' property"); + is(event.data, sentMessage, "Got correct message"); + ok(receivedMessage === undefined, "Haven't gotten message yet"); + receivedMessage = event.data; + if (receivedError) { + SimpleTest.finish(); + } + }; - worker.onerror = function(event) { - ok(event instanceof ErrorEvent, "Got an ErrorEvent"); - is(event.message, "Error: " + sentMessage, "Got correct error"); - is(event.filename, errorFilename, "Got correct filename"); - is(event.lineno, errorLine, "Got correct lineno"); - is(event.colno, errorColumn, "Got correct column"); - ok(receivedError === undefined, "Haven't gotten error yet"); - receivedError = event.message; - event.preventDefault(); - if (receivedMessage) { - SimpleTest.finish(); - } - }; + worker.onerror = function(event) { + ok(event instanceof ErrorEvent, "Got an ErrorEvent"); + is(event.message, "Error: " + sentMessage, "Got correct error"); + is(event.filename, errorFilename, "Got correct filename"); + is(event.lineno, errorLine, "Got correct lineno"); + is(event.colno, errorColumn, "Got correct column"); + ok(receivedError === undefined, "Haven't gotten error yet"); + receivedError = event.message; + event.preventDefault(); + if (receivedMessage) { + SimpleTest.finish(); + } + }; - worker.port.postMessage(sentMessage); - }); + worker.port.postMessage(sentMessage); SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/dom/workers/test/test_sharedWorker_lifetime.html +++ b/dom/workers/test/test_sharedWorker_lifetime.html @@ -8,25 +8,23 @@ <title>Test for MessagePort and SharedWorkers</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> </head> <body> <script class="testbody" type="text/javascript"> var gced = false; -SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]}, -function() { - var sw = new SharedWorker('sharedWorker_lifetime.js'); - sw.port.onmessage = function(event) { - ok(gced, "The SW is still alive also after GC"); - SimpleTest.finish(); - } - sw = null; - SpecialPowers.forceGC(); - gced = true; -}); +var sw = new SharedWorker('sharedWorker_lifetime.js'); +sw.port.onmessage = function(event) { + ok(gced, "The SW is still alive also after GC"); + SimpleTest.finish(); +} + +sw = null; +SpecialPowers.forceGC(); +gced = true; SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/workers/test/test_sharedWorker_performance_user_timing.html +++ b/dom/workers/test/test_sharedWorker_performance_user_timing.html @@ -7,24 +7,21 @@ <head> <title>Test for worker performance timing API</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> </head> <body> <script class="testbody" type="text/javascript"> -SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]}, -function() { - var sw = new SharedWorker('sharedworker_performance_user_timing.js'); - sw.port.onmessage = function(event) { - if (event.data.type == 'finish') { - SimpleTest.finish(); - } else if (event.data.type == 'status') { - ok(event.data.status, event.data.msg); - } +var sw = new SharedWorker('sharedworker_performance_user_timing.js'); +sw.port.onmessage = function(event) { + if (event.data.type == 'finish') { + SimpleTest.finish(); + } else if (event.data.type == 'status') { + ok(event.data.status, event.data.msg); } -}); +} SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/workers/test/test_sharedWorker_ports.html +++ b/dom/workers/test/test_sharedWorker_ports.html @@ -7,36 +7,33 @@ <head> <title>Test for MessagePort and SharedWorkers</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> </head> <body> <script class="testbody" type="text/javascript"> -SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]}, -function() { - var sw1 = new SharedWorker('sharedWorker_ports.js'); - sw1.port.onmessage = function(event) { - if (event.data.type == "connected") { - ok(true, "The SharedWorker is alive."); +var sw1 = new SharedWorker('sharedWorker_ports.js'); +sw1.port.onmessage = function(event) { + if (event.data.type == "connected") { + ok(true, "The SharedWorker is alive."); + + var sw2 = new SharedWorker('sharedWorker_ports.js'); + sw1.port.postMessage("Port from the main-thread!", [sw2.port]); + return; + } - var sw2 = new SharedWorker('sharedWorker_ports.js'); - sw1.port.postMessage("Port from the main-thread!", [sw2.port]); - return; - } + if (event.data.type == "status") { + ok(event.data.test, event.data.msg); + return; + } - if (event.data.type == "status") { - ok(event.data.test, event.data.msg); - return; - } - - if (event.data.type == "finish") { - SimpleTest.finish(); - } + if (event.data.type == "finish") { + SimpleTest.finish(); } -}); +} SimpleTest.waitForExplicitFinish(); </script> </body> </html>
--- a/dom/workers/test/test_sharedWorker_privateBrowsing.html +++ b/dom/workers/test/test_sharedWorker_privateBrowsing.html @@ -87,16 +87,15 @@ function runTest() { } var step = steps.shift(); step(); } SimpleTest.waitForExplicitFinish(); SpecialPowers.pushPrefEnv({"set": [ - ["dom.workers.sharedWorkers.enabled", true], ["browser.startup.page", 0], ["browser.startup.homepage_override.mstone", "ignore"], ]}, runTest); </script> </body> </html>
--- a/dom/workers/test/test_webSocket_sharedWorker.html +++ b/dom/workers/test/test_webSocket_sharedWorker.html @@ -8,26 +8,23 @@ <title>Test for bug 1090183</title> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body> <script class="testbody" type="text/javascript"> -SpecialPowers.pushPrefEnv({ set: [["dom.workers.sharedWorkers.enabled", true]]}, -function() { - var sw = new SharedWorker('webSocket_sharedWorker.js'); - sw.port.onmessage = function(event) { - if (event.data.type == 'finish') { - SimpleTest.finish(); - } else if (event.data.type == 'status') { - ok(event.data.status, event.data.msg); - } +var sw = new SharedWorker('webSocket_sharedWorker.js'); +sw.port.onmessage = function(event) { + if (event.data.type == 'finish') { + SimpleTest.finish(); + } else if (event.data.type == 'status') { + ok(event.data.status, event.data.msg); } -}); +} SimpleTest.waitForExplicitFinish(); </script> </pre> </body> </html>
--- a/dom/xbl/nsXBLPrototypeBinding.h +++ b/dom/xbl/nsXBLPrototypeBinding.h @@ -13,16 +13,17 @@ #include "nsICSSLoaderObserver.h" #include "nsInterfaceHashtable.h" #include "nsWeakReference.h" #include "nsXBLDocumentInfo.h" #include "nsXBLProtoImpl.h" #include "nsXBLProtoImplMethod.h" #include "nsXBLPrototypeHandler.h" #include "nsXBLPrototypeResources.h" +#include "mozilla/WeakPtr.h" class nsIAtom; class nsIContent; class nsIDocument; class nsXBLAttributeEntry; class nsXBLBinding; class nsXBLProtoImplField; @@ -31,19 +32,22 @@ class CSSStyleSheet; } // namespace mozilla // *********************************************************************/ // The XBLPrototypeBinding class // Instances of this class are owned by the nsXBLDocumentInfo object returned // by XBLDocumentInfo(). Consumers who want to refcount things should refcount // that. -class nsXBLPrototypeBinding final +class nsXBLPrototypeBinding final : + public mozilla::SupportsWeakPtr<nsXBLPrototypeBinding> { public: + MOZ_DECLARE_WEAKREFERENCE_TYPENAME(nsXBLPrototypeBinding) + nsIContent* GetBindingElement() const { return mBinding; } void SetBindingElement(nsIContent* aElement); nsIURI* BindingURI() const { return mBindingURI; } nsIURI* AlternateBindingURI() const { return mAlternateBindingURI; } nsIURI* DocURI() const { return mXBLDocInfoWeak->DocumentURI(); } nsIURI* GetBaseBindingURI() const { return mBaseBindingURI; } @@ -285,17 +289,18 @@ protected: nsAutoPtr<nsXBLPrototypeHandler> mPrototypeHandler; // Strong. DocInfo owns us, and we own the handlers. // the url of the base binding nsCOMPtr<nsIURI> mBaseBindingURI; nsXBLProtoImpl* mImplementation; // Our prototype implementation (includes methods, properties, fields, // the constructor, and the destructor). - nsXBLPrototypeBinding* mBaseBinding; // Weak. The docinfo will own our base binding. + // Weak. The docinfo will own our base binding. + mozilla::WeakPtr<nsXBLPrototypeBinding> mBaseBinding; bool mInheritStyle; bool mCheckedBaseProto; bool mKeyHandlersRegistered; bool mChromeOnlyContent; bool mBindToUntrustedContent; nsAutoPtr<nsXBLPrototypeResources> mResources; // If we have any resources, this will be non-null.
--- a/dom/xbl/nsXBLService.cpp +++ b/dom/xbl/nsXBLService.cpp @@ -725,17 +725,18 @@ nsXBLService::GetBinding(nsIContent* aBo nsresult rv = LoadBindingDocumentInfo(aBoundElement, boundDocument, aURI, aOriginPrincipal, false, getter_AddRefs(docInfo)); NS_ENSURE_SUCCESS(rv, rv); if (!docInfo) return NS_ERROR_FAILURE; - nsXBLPrototypeBinding* protoBinding = docInfo->GetPrototypeBinding(ref); + WeakPtr<nsXBLPrototypeBinding> protoBinding = + docInfo->GetPrototypeBinding(ref); if (!protoBinding) { #ifdef DEBUG nsAutoCString uriSpec; aURI->GetSpec(uriSpec); nsAutoCString doc; boundDocument->GetDocumentURI()->GetSpec(doc); nsAutoCString message("Unable to locate an XBL binding for URI "); @@ -776,17 +777,17 @@ nsXBLService::GetBinding(nsIContent* aBo protoBinding->AddResourceListener(aBoundElement); return NS_ERROR_FAILURE; // The binding isn't ready yet. } rv = protoBinding->ResolveBaseBinding(); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIURI> baseBindingURI; - nsXBLPrototypeBinding* baseProto = protoBinding->GetBasePrototype(); + WeakPtr<nsXBLPrototypeBinding> baseProto = protoBinding->GetBasePrototype(); if (baseProto) { baseBindingURI = baseProto->BindingURI(); } else { baseBindingURI = protoBinding->GetBaseBindingURI(); if (baseBindingURI) { uint32_t count = aDontExtendURIs.Length(); for (uint32_t index = 0; index < count; ++index) { @@ -821,17 +822,16 @@ nsXBLService::GetBinding(nsIContent* aBo if (NS_FAILED(rv)) return rv; // We aren't ready yet. } *aIsReady = true; if (!aPeekOnly) { // Make a new binding - protoBinding = docInfo->GetPrototypeBinding(ref); NS_ENSURE_STATE(protoBinding); nsXBLBinding *newBinding = new nsXBLBinding(protoBinding); if (baseBinding) { if (!baseProto) { protoBinding->SetBasePrototype(baseBinding->PrototypeBinding()); } newBinding->SetBaseBinding(baseBinding);
--- a/dom/xul/XULDocument.cpp +++ b/dom/xul/XULDocument.cpp @@ -913,16 +913,33 @@ XULDocument::AttributeWillChange(nsIDocu // See if we need to update our ref map. if (aAttribute == nsGkAtoms::ref) { // Might not need this, but be safe for now. nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this); RemoveElementFromRefMap(aElement); } } +static bool +ShouldPersistAttribute(nsIDocument* aDocument, Element* aElement) +{ + if (aElement->IsXULElement(nsGkAtoms::window)) { + if (nsCOMPtr<nsPIDOMWindow> window = aDocument->GetWindow()) { + bool isFullscreen; + window->GetFullScreen(&isFullscreen); + if (isFullscreen) { + // Don't persist attributes if it is window element and + // we are in fullscreen state. + return false; + } + } + } + return true; +} + void XULDocument::AttributeChanged(nsIDocument* aDocument, Element* aElement, int32_t aNameSpaceID, nsIAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { NS_ASSERTION(aDocument == this, "unexpected doc"); @@ -987,28 +1004,29 @@ XULDocument::AttributeChanged(nsIDocumen } } } // checks for modifications in broadcasters bool listener, resolved; CheckBroadcasterHookup(aElement, &listener, &resolved); - // See if there is anything we need to persist in the localstore. - // - // XXX Namespace handling broken :-( - nsAutoString persist; - aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist); - if (!persist.IsEmpty()) { - // XXXldb This should check that it's a token, not just a substring. - if (persist.Find(nsDependentAtomString(aAttribute)) >= 0) { + if (ShouldPersistAttribute(aDocument, aElement)) { + // See if there is anything we need to persist in the localstore. + // + // XXX Namespace handling broken :-( + nsString persist; + aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::persist, persist); + if (!persist.IsEmpty() && + // XXXldb This should check that it's a token, not just a substring. + persist.Find(nsDependentAtomString(aAttribute)) >= 0) { nsContentUtils::AddScriptRunner(NS_NewRunnableMethodWithArgs - <nsIContent*, int32_t, nsIAtom*> - (this, &XULDocument::DoPersist, aElement, kNameSpaceID_None, - aAttribute)); + <nsIContent*, int32_t, nsIAtom*> + (this, &XULDocument::DoPersist, aElement, + kNameSpaceID_None, aAttribute)); } } } void XULDocument::ContentAppended(nsIDocument* aDocument, nsIContent* aContainer, nsIContent* aFirstNewContent,
--- a/gfx/2d/SourceSurfaceSkia.cpp +++ b/gfx/2d/SourceSurfaceSkia.cpp @@ -101,17 +101,17 @@ SourceSurfaceSkia::InitFromTexture(DrawT SurfaceFormat aFormat) { MOZ_ASSERT(aOwner, "null GrContext"); #ifdef USE_SKIA_GPU GrBackendTextureDesc skiaTexGlue; mSize.width = skiaTexGlue.fWidth = aSize.width; mSize.height = skiaTexGlue.fHeight = aSize.height; skiaTexGlue.fFlags = kNone_GrBackendTextureFlag; - skiaTexGlue.fOrigin = kBottomLeft_GrSurfaceOrigin; + skiaTexGlue.fOrigin = kTopLeft_GrSurfaceOrigin; skiaTexGlue.fConfig = GfxFormatToGrConfig(aFormat); skiaTexGlue.fSampleCnt = 0; skiaTexGlue.fTextureHandle = aTexture; GrTexture *skiaTexture = aOwner->mGrContext->wrapBackendTexture(skiaTexGlue); SkImageInfo imgInfo = SkImageInfo::Make(aSize.width, aSize.height, GfxFormatToSkiaColorType(aFormat), kOpaque_SkAlphaType); SkGrPixelRef *texRef = new SkGrPixelRef(imgInfo, skiaTexture, false); mBitmap.setInfo(imgInfo, aSize.width*aSize.height*4);
--- a/gfx/layers/apz/src/WheelScrollAnimation.cpp +++ b/gfx/layers/apz/src/WheelScrollAnimation.cpp @@ -73,16 +73,17 @@ WheelScrollAnimation::InitPreferences(Ti { if (!mIsFirstIteration) { return; } mOriginMaxMS = clamped(gfxPrefs::WheelSmoothScrollMaxDurationMs(), 0, 10000); mOriginMinMS = clamped(gfxPrefs::WheelSmoothScrollMinDurationMs(), 0, mOriginMaxMS); - mIntervalRatio = (gfxPrefs::SmoothScrollDurationToIntervalRatio() * 100) / 100.0; + // The pref is 100-based int percentage, while mIntervalRatio is 1-based ratio + mIntervalRatio = ((double)gfxPrefs::SmoothScrollDurationToIntervalRatio()) / 100.0; mIntervalRatio = std::max(1.0, mIntervalRatio); InitializeHistory(aTime); } } // namespace layers } // namespace mozilla
--- a/gfx/layers/opengl/OGLShaderProgram.cpp +++ b/gfx/layers/opengl/OGLShaderProgram.cpp @@ -164,22 +164,27 @@ ShaderConfigOGL::SetDEAA(bool aEnabled) /* static */ ProgramProfileOGL ProgramProfileOGL::GetProfileFor(ShaderConfigOGL aConfig) { ProgramProfileOGL result; ostringstream fs, vs; AddUniforms(result); + vs << "#ifdef GL_ES" << endl; + vs << "#define EDGE_PRECISION mediump" << endl; + vs << "#else" << endl; + vs << "#define EDGE_PRECISION" << endl; + vs << "#endif" << endl; vs << "uniform mat4 uMatrixProj;" << endl; vs << "uniform vec4 uLayerRects[4];" << endl; vs << "uniform mat4 uLayerTransform;" << endl; if (aConfig.mFeatures & ENABLE_DEAA) { vs << "uniform mat4 uLayerTransformInverse;" << endl; - vs << "uniform vec3 uSSEdges[4];" << endl; + vs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl; vs << "uniform vec2 uVisibleCenter;" << endl; vs << "uniform vec2 uViewportSize;" << endl; } vs << "uniform vec2 uRenderTargetOffset;" << endl; vs << "attribute vec4 aCoord;" << endl; if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { vs << "uniform mat4 uTextureTransform;" << endl; @@ -277,18 +282,20 @@ ProgramProfileOGL::GetProfileFor(ShaderC fs << "#extension GL_ARB_texture_rectangle : require" << endl; } if (aConfig.mFeatures & ENABLE_TEXTURE_EXTERNAL) { fs << "#extension GL_OES_EGL_image_external : require" << endl; } fs << "#ifdef GL_ES" << endl; fs << "precision mediump float;" << endl; fs << "#define COLOR_PRECISION lowp" << endl; + fs << "#define EDGE_PRECISION mediump" << endl; fs << "#else" << endl; fs << "#define COLOR_PRECISION" << endl; + fs << "#define EDGE_PRECISION" << endl; fs << "#endif" << endl; if (aConfig.mFeatures & ENABLE_RENDER_COLOR) { fs << "uniform COLOR_PRECISION vec4 uRenderColor;" << endl; } else { // for tiling, texcoord can be greater than the lowfp range fs << "varying vec2 vTexCoord;" << endl; if (aConfig.mFeatures & ENABLE_BLUR) { fs << "uniform bool uBlurAlpha;" << endl; @@ -339,17 +346,17 @@ ProgramProfileOGL::GetProfileFor(ShaderC if (aConfig.mFeatures & ENABLE_MASK_2D || aConfig.mFeatures & ENABLE_MASK_3D) { fs << "varying vec3 vMaskCoord;" << endl; fs << "uniform sampler2D uMaskTexture;" << endl; } if (aConfig.mFeatures & ENABLE_DEAA) { - fs << "uniform vec3 uSSEdges[4];" << endl; + fs << "uniform EDGE_PRECISION vec3 uSSEdges[4];" << endl; } if (!(aConfig.mFeatures & ENABLE_RENDER_COLOR)) { fs << "vec4 sample(vec2 coord) {" << endl; fs << " vec4 color;" << endl; if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR || aConfig.mFeatures & ENABLE_TEXTURE_NV12) { if (aConfig.mFeatures & ENABLE_TEXTURE_YCBCR) {
--- a/gfx/src/nsFontMetrics.cpp +++ b/gfx/src/nsFontMetrics.cpp @@ -61,17 +61,17 @@ private: if (aMetrics->GetVertical()) { switch (aMetrics->GetTextOrientation()) { case NS_STYLE_TEXT_ORIENTATION_MIXED: flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED; break; case NS_STYLE_TEXT_ORIENTATION_UPRIGHT: flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT; break; - case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT: + case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS: flags |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; break; } } return flags; } nsAutoPtr<gfxTextRun> mTextRun;
--- a/gfx/tests/reftest/1143303-1.svg +++ b/gfx/tests/reftest/1143303-1.svg @@ -1,20 +1,14 @@ <!-- Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ --> <svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> -<!-- There is a bug with MultiTiledContentClient that causes improper painting; - bug 1204076 is on file for this issue. As a temporary workaround, we set - user-scalable=no here so that WantAsyncZoom() returns false and we don't - use a MultiTiledContentClient. Once bug 1204076 is fixed, we can remove this. --> - <meta xmlns="http://www.w3.org/1999/xhtml" name="viewport" content="user-scalable=no"/> - <title>Testcase for small circles</title> <!--From https://bugzilla.mozilla.org/show_bug.cgi?id=1143303 --> <rect width="100%" height="100%" fill="lime"/> <circle cx="200" cy="150" r="95" fill="red"/> <g transform="translate(200, 150)" fill="lime"> <g transform="scale(1e8, 1e8)">
--- a/ipc/chromium/moz.build +++ b/ipc/chromium/moz.build @@ -1,44 +1,16 @@ # -*- 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/. -os_win = 0 -os_posix = 0 -os_macosx = 0 -os_dragonfly = 0 -os_freebsd = 0 -os_netbsd = 0 -os_openbsd = 0 -os_bsd = 0 -os_linux = 0 - -if CONFIG['OS_ARCH'] == 'WINNT': - os_win = 1 -else: - os_posix = 1 - if CONFIG['OS_ARCH'] == 'Darwin': - os_macosx = 1 - elif CONFIG['OS_ARCH'] == 'DragonFly': - os_dragonfly = 1 - os_bsd = 1 - elif CONFIG['OS_ARCH'] in ['FreeBSD', 'GNU_kFreeBSD']: - os_freebsd = 1 - os_bsd = 1 - elif CONFIG['OS_ARCH'] == 'NetBSD': - os_netbsd = 1 - os_bsd = 1 - elif CONFIG['OS_ARCH'] == 'OpenBSD': - os_openbsd = 1 - os_bsd = 1 - else: - os_linux = 1 +libevent_path_prefix = 'src/third_party' +include(libevent_path_prefix + '/libeventcommon.mozbuild') UNIFIED_SOURCES += [ 'src/base/at_exit.cc', 'src/base/base_switches.cc', 'src/base/command_line.cc', 'src/base/file_path.cc', 'src/base/file_util.cc', 'src/base/histogram.cc', @@ -97,46 +69,17 @@ if os_win: 'src/base/waitable_event_watcher_win.cc', 'src/base/waitable_event_win.cc', 'src/base/win_util.cc', 'src/chrome/common/ipc_channel_win.cc', 'src/chrome/common/process_watcher_win.cc', 'src/chrome/common/transport_dib_win.cc', ] elif not CONFIG['MOZ_NATIVE_LIBEVENT']: - UNIFIED_SOURCES += [ - 'src/third_party/libevent/buffer.c', - 'src/third_party/libevent/bufferevent.c', - 'src/third_party/libevent/bufferevent_ratelim.c', - 'src/third_party/libevent/bufferevent_sock.c', - 'src/third_party/libevent/event.c', - 'src/third_party/libevent/event_tagging.c', - 'src/third_party/libevent/evmap.c', - 'src/third_party/libevent/evrpc.c', - 'src/third_party/libevent/evthread.c', - 'src/third_party/libevent/evthread_pthread.c', - 'src/third_party/libevent/evutil.c', - 'src/third_party/libevent/evutil_rand.c', - 'src/third_party/libevent/http.c', - 'src/third_party/libevent/listener.c', - 'src/third_party/libevent/log.c', - 'src/third_party/libevent/poll.c', - 'src/third_party/libevent/select.c', - 'src/third_party/libevent/signal.c', - 'src/third_party/libevent/strlcpy.c', - ] - SOURCES += [ - # This file cannot be built in unified mode because of strtotimeval symbol clash. - 'src/third_party/libevent/evdns.c', - ] - DEFINES['HAVE_CONFIG_H'] = True - LOCAL_INCLUDES += [ - 'src/third_party/libevent', - 'src/third_party/libevent/include', - ] + DIRS += ['src/third_party'] if os_posix: UNIFIED_SOURCES += [ 'src/base/condition_variable_posix.cc', 'src/base/file_descriptor_shuffle.cc', 'src/base/file_util_posix.cc', 'src/base/lock_impl_posix.cc', 'src/base/message_pump_libevent.cc', @@ -151,22 +94,16 @@ if os_posix: 'src/base/thread_local_posix.cc', 'src/base/thread_local_storage_posix.cc', 'src/base/waitable_event_posix.cc', 'src/base/waitable_event_watcher_posix.cc', 'src/chrome/common/file_descriptor_set_posix.cc', 'src/chrome/common/ipc_channel_posix.cc', 'src/chrome/common/process_watcher_posix_sigchld.cc', ] - if CONFIG['OS_TARGET'] == 'Android': - UNIFIED_SOURCES += [ - 'src/base/message_pump_android.cc', - ] - DEFINES['ANDROID'] = True - DEFINES['_POSIX_MONOTONIC_CLOCK'] = 0 if os_macosx: UNIFIED_SOURCES += [ 'src/base/chrome_application_mac.mm', 'src/base/file_util_mac.mm', 'src/base/idle_timer.cc', 'src/base/mac_util.mm', 'src/base/message_pump_mac.mm', @@ -175,81 +112,59 @@ if os_macosx: 'src/base/sys_info_mac.cc', 'src/base/sys_string_conversions_mac.mm', 'src/base/time_mac.cc', 'src/chrome/common/mach_ipc_mac.mm', 'src/chrome/common/mach_message_source_mac.cc', 'src/chrome/common/transport_dib_mac.cc', ] SOURCES += [ - # This file cannot be built in unified mode because of the redefinition of NoOp. + # This file cannot be built in unified mode because of the redefinition + # of NoOp. 'src/base/platform_thread_mac.mm', ] - if not CONFIG['MOZ_NATIVE_LIBEVENT']: - UNIFIED_SOURCES += [ - 'src/third_party/libevent/kqueue.c', - ] - LOCAL_INCLUDES += ['src/third_party/libevent/mac'] - -if os_linux: - SOURCES += [ - 'src/base/atomicops_internals_x86_gcc.cc', - 'src/base/idle_timer_none.cc', - 'src/base/process_util_linux.cc', - 'src/base/time_posix.cc', - ] - if CONFIG['MOZ_WIDGET_GTK']: - SOURCES += [ - 'src/base/message_pump_glib.cc', - ] - if CONFIG['MOZ_ENABLE_QT']: - SOURCES += [ - '!moc_message_pump_qt.cc', - 'src/base/message_pump_qt.cc', - ] - if not CONFIG['MOZ_NATIVE_LIBEVENT']: - if CONFIG['OS_TARGET'] != 'Android': - SOURCES += [ - 'src/third_party/libevent/epoll_sub.c', - ] - SOURCES += [ - 'src/third_party/libevent/epoll.c', - ] - if CONFIG['OS_TARGET'] == 'Android': - LOCAL_INCLUDES += ['src/third_party/libevent/android'] - else: - LOCAL_INCLUDES += ['src/third_party/libevent/linux'] if os_bsd: SOURCES += [ 'src/base/atomicops_internals_x86_gcc.cc', 'src/base/time_posix.cc', ] if CONFIG['OS_ARCH'] == 'GNU_kFreeBSD': SOURCES += [ 'src/base/process_util_linux.cc' ] else: SOURCES += [ 'src/base/process_util_bsd.cc' ] + +if os_linux: + SOURCES += [ + 'src/base/atomicops_internals_x86_gcc.cc', + 'src/base/idle_timer_none.cc', + 'src/base/process_util_linux.cc', + 'src/base/time_posix.cc', + ] + if CONFIG['OS_TARGET'] == 'Android': + UNIFIED_SOURCES += [ + 'src/base/message_pump_android.cc', + ] + DEFINES['ANDROID'] = True + DEFINES['_POSIX_MONOTONIC_CLOCK'] = 0 + +if os_bsd or os_linux: if CONFIG['MOZ_WIDGET_GTK']: SOURCES += [ 'src/base/message_pump_glib.cc', ] if CONFIG['MOZ_ENABLE_QT']: SOURCES += [ '!moc_message_pump_qt.cc', 'src/base/message_pump_qt.cc', ] - if not CONFIG['MOZ_NATIVE_LIBEVENT']: - SOURCES += [ - 'src/third_party/libevent/kqueue.c', - ] - LOCAL_INCLUDES += ['src/third_party/libevent/bsd'] ost = CONFIG['OS_TEST'] if '86' not in ost and 'arm' not in ost and 'mips' not in ost: SOURCES += [ 'src/base/atomicops_internals_mutex.cc', ] include('/ipc/chromium/chromium-config.mozbuild')
new file mode 100644 --- /dev/null +++ b/ipc/chromium/src/third_party/libeventcommon.mozbuild @@ -0,0 +1,37 @@ +# -*- 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/. + +os_win = 0 +os_posix = 0 +os_macosx = 0 +os_bsd = 0 +os_linux = 0 + +if CONFIG['OS_ARCH'] == 'WINNT': + os_win = 1 +else: + os_posix = 1 + if CONFIG['OS_ARCH'] == 'Darwin': + os_macosx = 1 + libevent_include_suffix = 'mac' + elif CONFIG['OS_ARCH'] in ['DragonFly', 'FreeBSD', 'GNU_kFreeBSD', + 'NetBSD', 'OpenBSD']: + os_bsd = 1 + libevent_include_suffix = 'bsd' + else: + os_linux = 1 + if CONFIG['OS_TARGET'] == 'Android': + libevent_include_suffix = 'android' + else: + libevent_include_suffix = 'linux' + +if os_posix and not CONFIG['MOZ_NATIVE_LIBEVENT']: + DEFINES['HAVE_CONFIG_H'] = True + LOCAL_INCLUDES += sorted([ + libevent_path_prefix + '/libevent', + libevent_path_prefix + '/libevent/include', + libevent_path_prefix + '/libevent/' + libevent_include_suffix, + ])
new file mode 100644 --- /dev/null +++ b/ipc/chromium/src/third_party/moz.build @@ -0,0 +1,59 @@ +# -*- 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/. + +libevent_path_prefix = '.' +include(libevent_path_prefix + '/libeventcommon.mozbuild') + +if os_win: + error('should not reach here on Windows') + +if CONFIG['MOZ_NATIVE_LIBEVENT']: + error('should not reach here if we are using a native libevent') + +UNIFIED_SOURCES += [ + 'libevent/buffer.c', + 'libevent/bufferevent.c', + 'libevent/bufferevent_ratelim.c', + 'libevent/bufferevent_sock.c', + 'libevent/event.c', + 'libevent/event_tagging.c', + 'libevent/evmap.c', + 'libevent/evrpc.c', + 'libevent/evthread.c', + 'libevent/evthread_pthread.c', + 'libevent/evutil.c', + 'libevent/evutil_rand.c', + 'libevent/http.c', + 'libevent/listener.c', + 'libevent/log.c', + 'libevent/poll.c', + 'libevent/select.c', + 'libevent/signal.c', + 'libevent/strlcpy.c', +] +SOURCES += [ + # This file cannot be built in unified mode because of strtotimeval + # symbol clash. + 'libevent/evdns.c', +] + +if os_macosx or os_bsd: + UNIFIED_SOURCES += [ + 'libevent/kqueue.c', + ] + +if os_linux: + SOURCES += [ + 'libevent/epoll.c', + ] + if CONFIG['OS_TARGET'] != 'Android': + SOURCES += [ + 'libevent/epoll_sub.c', + ] + +ALLOW_COMPILER_WARNINGS = True + +FINAL_LIBRARY = 'xul'
--- a/ipc/glue/CrossProcessMutex.h +++ b/ipc/glue/CrossProcessMutex.h @@ -4,17 +4,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef mozilla_CrossProcessMutex_h #define mozilla_CrossProcessMutex_h #include "base/process.h" #include "mozilla/Mutex.h" -#if defined(OS_LINUX) || defined(OS_MACOSX) +#if defined(OS_LINUX) || defined(XP_DARWIN) #include <pthread.h> #include "SharedMemoryBasic.h" #include "mozilla/Atomics.h" #include "nsAutoPtr.h" #endif namespace IPC { template<typename T>
new file mode 100644 --- /dev/null +++ b/ipc/glue/Makefile.in @@ -0,0 +1,1 @@ +%/SharedMemoryBasic_mach.cpp: ;
--- a/ipc/glue/SharedMemoryBasic.h +++ b/ipc/glue/SharedMemoryBasic.h @@ -5,15 +5,15 @@ * 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_ipc_SharedMemoryBasic_h #define mozilla_ipc_SharedMemoryBasic_h #ifdef ANDROID # include "mozilla/ipc/SharedMemoryBasic_android.h" -#elif defined(XP_MACOSX) +#elif defined(XP_DARWIN) # include "mozilla/ipc/SharedMemoryBasic_mach.h" #else # include "mozilla/ipc/SharedMemoryBasic_chromium.h" #endif #endif // ifndef mozilla_ipc_SharedMemoryBasic_h
rename from ipc/glue/SharedMemoryBasic_mach.cpp rename to ipc/glue/SharedMemoryBasic_mach.mm --- a/ipc/glue/SharedMemoryBasic_mach.cpp +++ b/ipc/glue/SharedMemoryBasic_mach.mm @@ -4,17 +4,28 @@ /* 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 <map> #include <mach/vm_map.h> #include <mach/mach_port.h> +#if defined(XP_IOS) +#include <mach/vm_map.h> +#define mach_vm_address_t vm_address_t +#define mach_vm_allocate vm_allocate +#define mach_vm_deallocate vm_deallocate +#define mach_vm_map vm_map +#define mach_vm_read vm_read +#define mach_vm_region_recurse vm_region_recurse_64 +#define mach_vm_size_t vm_size_t +#else #include <mach/mach_vm.h> +#endif #include <pthread.h> #include <unistd.h> #include "SharedMemoryBasic.h" #include "chrome/common/mach_ipc_mac.h" #include "mozilla/StaticMutex.h" #ifdef DEBUG
--- a/ipc/glue/moz.build +++ b/ipc/glue/moz.build @@ -75,17 +75,17 @@ else: if CONFIG['OS_TARGET'] == 'Android': EXPORTS.mozilla.ipc += ['SharedMemoryBasic_android.h'] UNIFIED_SOURCES += [ 'SharedMemoryBasic_android.cpp', ] elif CONFIG['OS_ARCH'] == 'Darwin': EXPORTS.mozilla.ipc += ['SharedMemoryBasic_mach.h'] SOURCES += [ - 'SharedMemoryBasic_mach.cpp', + 'SharedMemoryBasic_mach.mm', ] else: EXPORTS.mozilla.ipc += ['SharedMemoryBasic_chromium.h'] if CONFIG['OS_ARCH'] == 'Linux': UNIFIED_SOURCES += [ 'ProcessUtils_linux.cpp', ]
--- a/js/public/Class.h +++ b/js/public/Class.h @@ -698,17 +698,18 @@ struct JSClass { // Implementing this efficiently requires that global objects have classes // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was // previously allowed, but is now an ES5 violation and thus unsupported. // // JSCLASS_GLOBAL_APPLICATION_SLOTS is the number of slots reserved at // the beginning of every global object's slots for use by the // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 -#define JSCLASS_GLOBAL_SLOT_COUNT (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 32) +#define JSCLASS_GLOBAL_SLOT_COUNT \ + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 33) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0) #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp) \ (((clasp)->flags & JSCLASS_IS_GLOBAL) \ && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
--- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -564,17 +564,17 @@ class Base { using Size = uint64_t; virtual Size size(mozilla::MallocSizeOf mallocSizeof) const { return 1; } // Return an EdgeRange that initially contains all the referent's outgoing // edges. The caller takes ownership of the EdgeRange. // // If wantNames is true, compute names for edges. Doing so can be expensive // in time and memory. - virtual UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const = 0; + virtual UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const = 0; // Return the Zone to which this node's referent belongs, or nullptr if the // referent is not of a type allocated in SpiderMonkey Zones. virtual JS::Zone* zone() const { return nullptr; } // Return the compartment for this node. Some ubi::Node referents are not // associated with JSCompartments, such as JSStrings (which are associated // with Zones). When the referent is not associated with a compartment, @@ -751,18 +751,18 @@ class Node { return base()->jsObjectConstructorName(cx, outName); } using Size = Base::Size; Size size(mozilla::MallocSizeOf mallocSizeof) const { return base()->size(mallocSizeof); } - UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames = true) const { - return base()->edges(cx, wantNames); + UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames = true) const { + return base()->edges(rt, wantNames); } bool hasAllocationStack() const { return base()->hasAllocationStack(); } StackFrame allocationStack() const { return base()->allocationStack(); } using Id = Base::Id; @@ -863,32 +863,32 @@ class EdgeRange { virtual void popFront() = 0; private: EdgeRange(const EdgeRange&) = delete; EdgeRange& operator=(const EdgeRange&) = delete; }; -typedef mozilla::Vector<Edge, 8, js::TempAllocPolicy> EdgeVector; +typedef mozilla::Vector<Edge, 8, js::SystemAllocPolicy> EdgeVector; // An EdgeRange concrete class that holds a pre-existing vector of // Edges. A PreComputedEdgeRange does not take ownership of its // EdgeVector; it is up to the PreComputedEdgeRange's consumer to manage // that lifetime. class PreComputedEdgeRange : public EdgeRange { EdgeVector& edges; size_t i; void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; } public: - explicit PreComputedEdgeRange(JSContext* cx, EdgeVector& edges) + explicit PreComputedEdgeRange(EdgeVector& edges) : edges(edges), i(0) { settle(); } void popFront() override { MOZ_ASSERT(!empty()); @@ -911,36 +911,36 @@ class PreComputedEdgeRange : public Edge // has been created, GC must not occur, as the referent ubi::Nodes are not // stable across GC. The init calls emplace on |noGC|'s AutoCheckCannotGC, whose // lifetime must extend at least as long as the RootList itself. // // Example usage: // // { // mozilla::Maybe<JS::AutoCheckCannotGC> maybeNoGC; -// JS::ubi::RootList rootList(cx, maybeNoGC); +// JS::ubi::RootList rootList(rt, maybeNoGC); // if (!rootList.init()) // return false; // // // The AutoCheckCannotGC is guaranteed to exist if init returned true. // MOZ_ASSERT(maybeNoGC.isSome()); // // JS::ubi::Node root(&rootList); // // ... // } class MOZ_STACK_CLASS RootList { Maybe<AutoCheckCannotGC>& noGC; - JSContext* cx; public: + JSRuntime* rt; EdgeVector edges; bool wantNames; - RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false); + RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames = false); // Find all GC roots. bool init(); // Find only GC roots in the provided set of |Zone|s. bool init(ZoneSet& debuggees); // Find only GC roots in the given Debugger object's set of debuggee zones. bool init(HandleObject debuggees); @@ -954,34 +954,34 @@ class MOZ_STACK_CLASS RootList { bool addRoot(Node node, const char16_t* edgeName = nullptr); }; /*** Concrete classes for ubi::Node referent types ************************************************/ template<> struct Concrete<RootList> : public Base { - UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; + UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override; const char16_t* typeName() const override { return concreteTypeName; } protected: explicit Concrete(RootList* ptr) : Base(ptr) { } RootList& get() const { return *static_cast<RootList*>(ptr); } public: static const char16_t concreteTypeName[]; static void construct(void* storage, RootList* ptr) { new (storage) Concrete(ptr); } }; // A reusable ubi::Concrete specialization base class for types supported by // JS::TraceChildren. template<typename Referent> class TracerConcrete : public Base { const char16_t* typeName() const override { return concreteTypeName; } - UniquePtr<EdgeRange> edges(JSContext*, bool wantNames) const override; + UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override; JS::Zone* zone() const override; protected: explicit TracerConcrete(Referent* ptr) : Base(ptr) { } Referent& get() const { return *static_cast<Referent*>(ptr); } public: static const char16_t concreteTypeName[]; @@ -1064,17 +1064,17 @@ template<> struct Concrete<JSString> : T static void construct(void *storage, JSString *ptr) { new (storage) Concrete(ptr); } }; // The ubi::Node null pointer. Any attempt to operate on a null ubi::Node asserts. template<> class Concrete<void> : public Base { const char16_t* typeName() const override; Size size(mozilla::MallocSizeOf mallocSizeOf) const override; - UniquePtr<EdgeRange> edges(JSContext* cx, bool wantNames) const override; + UniquePtr<EdgeRange> edges(JSRuntime* rt, bool wantNames) const override; JS::Zone* zone() const override; JSCompartment* compartment() const override; CoarseType coarseType() const final; explicit Concrete(void* ptr) : Base(ptr) { } public: static void construct(void* storage, void* ptr) { new (storage) Concrete(ptr); }
--- a/js/public/UbiNodeTraverse.h +++ b/js/public/UbiNodeTraverse.h @@ -73,23 +73,23 @@ namespace ubi { // The visitor function should return true on success, or false if an // error occurs. A false return value terminates the traversal // immediately, and causes BreadthFirst<Handler>::traverse to return // false. template<typename Handler> struct BreadthFirst { // Construct a breadth-first traversal object that reports the nodes it - // reaches to |handler|. The traversal object reports OOM on |cx|, and - // asserts that no GC happens in |cx|'s runtime during its lifetime. + // reaches to |handler|. The traversal asserts that no GC happens in its + // runtime during its lifetime. // // We do nothing with noGC, other than require it to exist, with a lifetime // that encloses our own. - BreadthFirst(JSContext* cx, Handler& handler, const JS::AutoCheckCannotGC& noGC) - : wantNames(true), cx(cx), visited(cx), handler(handler), pending(cx), + BreadthFirst(JSRuntime* rt, Handler& handler, const JS::AutoCheckCannotGC& noGC) + : wantNames(true), rt(rt), visited(), handler(handler), pending(), traversalBegun(false), stopRequested(false), abandonRequested(false) { } // Initialize this traversal object. Return false on OOM. bool init() { return visited.init(); } // Add |node| as a starting point for the traversal. You may add // as many starting points as you like. Return false on OOM. @@ -121,17 +121,17 @@ struct BreadthFirst { traversalBegun = true; // While there are pending nodes, visit them. while (!pending.empty()) { Node origin = pending.front(); pending.popFront(); // Get a range containing all origin's outgoing edges. - auto range = origin.edges(cx, wantNames); + auto range = origin.edges(rt, wantNames); if (!range) return false; // Traverse each edge. for (; !range->empty(); range->popFront()) { MOZ_ASSERT(!stopRequested); const Edge& edge = range->front(); @@ -176,38 +176,39 @@ struct BreadthFirst { // error. void stop() { stopRequested = true; } // Request that the current edge's referent's outgoing edges not be // traversed. This must be called the first time that referent is reached. // Other edges *to* that referent will still be traversed. void abandonReferent() { abandonRequested = true; } - // The context with which we were constructed. - JSContext* cx; + // The runtime with which we were constructed. + JSRuntime* rt; // A map associating each node N that we have reached with a // Handler::NodeData, for |handler|'s use. This is public, so that // |handler| can access it to see the traversal thus far. - typedef js::HashMap<Node, typename Handler::NodeData> NodeMap; + using NodeMap = js::HashMap<Node, typename Handler::NodeData, js::DefaultHasher<Node>, + js::SystemAllocPolicy>; NodeMap visited; private: // Our handler object. Handler& handler; // A queue template. Appending and popping the front are constant time. // Wasted space is never more than some recent actual population plus the // current population. template <typename T> class Queue { - js::Vector<T, 0> head, tail; + js::Vector<T, 0, js::SystemAllocPolicy> head, tail; size_t frontIndex; public: - explicit Queue(JSContext* cx) : head(cx), tail(cx), frontIndex(0) { } + Queue() : head(), tail(), frontIndex(0) { } bool empty() { return frontIndex >= head.length(); } T& front() { MOZ_ASSERT(!empty()); return head[frontIndex]; } void popFront() { MOZ_ASSERT(!empty()); frontIndex++;
--- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -286,16 +286,17 @@ selfhosting_srcs := \ $(srcdir)/builtin/Array.js \ $(srcdir)/builtin/Date.js \ $(srcdir)/builtin/Error.js \ $(srcdir)/builtin/Generator.js \ $(srcdir)/builtin/Intl.js \ $(srcdir)/builtin/IntlData.js \ $(srcdir)/builtin/Iterator.js \ $(srcdir)/builtin/Map.js \ + $(srcdir)/builtin/Module.js \ $(srcdir)/builtin/Number.js \ $(srcdir)/builtin/Object.js \ $(srcdir)/builtin/Reflect.js \ $(srcdir)/builtin/RegExp.js \ $(srcdir)/builtin/String.js \ $(srcdir)/builtin/Set.js \ $(srcdir)/builtin/TypedArray.js \ $(srcdir)/builtin/TypedObject.js \
--- a/js/src/asmjs/AsmJSSignalHandlers.cpp +++ b/js/src/asmjs/AsmJSSignalHandlers.cpp @@ -196,16 +196,17 @@ class AutoSetHandlingSignal # if defined(__FreeBSD__) && defined(__arm__) # define R15_sig(p) ((p)->uc_mcontext.__gregs[_REG_R15]) # else # define R15_sig(p) ((p)->uc_mcontext.mc_r15) # endif #elif defined(XP_DARWIN) # define EIP_sig(p) ((p)->uc_mcontext->__ss.__eip) # define RIP_sig(p) ((p)->uc_mcontext->__ss.__rip) +# define R15_sig(p) ((p)->uc_mcontext->__ss.__pc) #else # error "Don't know how to read/write to the thread state via the mcontext_t." #endif #if defined(XP_WIN) # include "jswin.h" #else # include <signal.h> @@ -315,22 +316,30 @@ enum { REG_EIP = 14 }; // sigaction-style signal handler. #if defined(XP_DARWIN) && defined(ASMJS_MAY_USE_SIGNAL_HANDLERS_FOR_OOB) # if defined(JS_CODEGEN_X64) struct macos_x64_context { x86_thread_state64_t thread; x86_float_state64_t float_; }; # define EMULATOR_CONTEXT macos_x64_context -# else +# elif defined(JS_CODEGEN_X86) struct macos_x86_context { x86_thread_state_t thread; x86_float_state_t float_; }; # define EMULATOR_CONTEXT macos_x86_context +# elif defined(JS_CODEGEN_ARM) +struct macos_arm_context { + arm_thread_state_t thread; + arm_neon_state_t float_; +}; +# define EMULATOR_CONTEXT macos_arm_context +# else +# error Unsupported architecture # endif #else # define EMULATOR_CONTEXT CONTEXT #endif #if defined(JS_CPU_X64) # define PC_sig(p) RIP_sig(p) #elif defined(JS_CPU_X86) @@ -798,20 +807,26 @@ AsmJSFaultHandler(LPEXCEPTION_POINTERS e static uint8_t** ContextToPC(EMULATOR_CONTEXT* context) { # if defined(JS_CPU_X64) static_assert(sizeof(context->thread.__rip) == sizeof(void*), "stored IP should be compile-time pointer-sized"); return reinterpret_cast<uint8_t**>(&context->thread.__rip); -# else +# elif defined(JS_CPU_X86) static_assert(sizeof(context->thread.uts.ts32.__eip) == sizeof(void*), "stored IP should be compile-time pointer-sized"); return reinterpret_cast<uint8_t**>(&context->thread.uts.ts32.__eip); +# elif defined(JS_CPU_ARM) + static_assert(sizeof(context->thread.__pc) == sizeof(void*), + "stored IP should be compile-time pointer-sized"); + return reinterpret_cast<uint8_t**>(&context->thread.__pc); +# else +# error Unsupported architecture # endif } // This definition was generated by mig (the Mach Interface Generator) for the // routine 'exception_raise' (exc.defs). #pragma pack(4) typedef struct { mach_msg_header_t Head; @@ -847,21 +862,28 @@ HandleMachException(JSRuntime* rt, const // Read out the JSRuntime thread's register state. EMULATOR_CONTEXT context; # if defined(JS_CODEGEN_X64) unsigned int thread_state_count = x86_THREAD_STATE64_COUNT; unsigned int float_state_count = x86_FLOAT_STATE64_COUNT; int thread_state = x86_THREAD_STATE64; int float_state = x86_FLOAT_STATE64; -# else +# elif defined(JS_CODEGEN_X86) unsigned int thread_state_count = x86_THREAD_STATE_COUNT; unsigned int float_state_count = x86_FLOAT_STATE_COUNT; int thread_state = x86_THREAD_STATE; int float_state = x86_FLOAT_STATE; +# elif defined(JS_CODEGEN_ARM) + unsigned int thread_state_count = ARM_THREAD_STATE_COUNT; + unsigned int float_state_count = ARM_NEON_STATE_COUNT; + int thread_state = ARM_THREAD_STATE; + int float_state = ARM_NEON_STATE; +# else +# error Unsupported architecture # endif kern_return_t kret; kret = thread_get_state(rtThread, thread_state, (thread_state_t)&context.thread, &thread_state_count); if (kret != KERN_SUCCESS) return false; kret = thread_get_state(rtThread, float_state, (thread_state_t)&context.float_, &float_state_count);
new file mode 100644 --- /dev/null +++ b/js/src/builtin/Module.js @@ -0,0 +1,171 @@ +/* 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/. */ + +function ModuleResolveExport(exportName, resolveSet = [], exportStarSet = []) +{ + // 15.2.1.16.3 ResolveExport(exportName, resolveSet, exportStarSet) + + if (!IsObject(this) || !IsModule(this)) { + return callFunction(CallModuleMethodIfWrapped, this, exportName, resolveSet, + exportStarSet, "ModuleResolveExport"); + } + + // Step 1 + let module = this; + + // Step 2 + for (let i = 0; i < resolveSet.length; i++) { + let r = resolveSet[i]; + if (r.module === module && r.exportName === exportName) + return null; + } + + // Step 3 + resolveSet.push({module: module, exportName: exportName}); + + // Step 4 + let localExportEntries = module.localExportEntries; + for (let i = 0; i < localExportEntries.length; i++) { + let e = localExportEntries[i]; + if (exportName === e.exportName) + return {module: module, bindingName: e.localName}; + } + + // Step 5 + let indirectExportEntries = module.indirectExportEntries; + for (let i = 0; i < indirectExportEntries.length; i++) { + let e = indirectExportEntries[i]; + if (exportName === e.exportName) { + let importedModule = HostResolveImportedModule(module, e.moduleRequest); + let indirectResolution = importedModule.resolveExport(e.importName, + resolveSet, + exportStarSet); + if (indirectResolution !== null) + return indirectResolution; + } + } + + // Step 6 + if (exportName === "default") { + // A default export cannot be provided by an export *. + ThrowSyntaxError(JSMSG_BAD_DEFAULT_EXPORT); + } + + // Step 7 + if (module in exportStarSet) + return null; + + // Step 8 + exportStarSet.push(module); + + // Step 9 + let starResolution = null; + + // Step 10 + let starExportEntries = module.starExportEntries; + for (let i = 0; i < starExportEntries.length; i++) { + let e = starExportEntries[i]; + let importedModule = HostResolveImportedModule(module, e.moduleRequest); + let resolution = importedModule.resolveExport(exportName, resolveSet, exportStarSet); + if (resolution === "ambiguous") + return resolution; + + if (resolution !== null) { + if (starResolution === null) { + starResolution = resolution; + } else { + if (resolution.module !== starResolution.module || + resolution.exportName !== starResolution.exportName) + { + return "ambiguous"; + } + } + } + } + + return starResolution; +} + +// 15.2.1.16.4 ModuleDeclarationInstantiation() +function ModuleDeclarationInstantiation() +{ + if (!IsObject(this) || !IsModule(this)) + return callFunction(CallModuleMethodIfWrapped, this, "ModuleDeclarationInstantiation"); + + // Step 1 + let module = this; + + // Step 5 + if (module.environment !== undefined) + return; + + // Step 7 + CreateModuleEnvironment(module); + let env = module.environment; + + // Step 8 + let requestedModules = module.requestedModules; + for (let i = 0; i < requestedModules.length; i++) { + let required = requestedModules[i]; + let requiredModule = HostResolveImportedModule(module, required); + requiredModule.declarationInstantiation(); + } + + // Step 9 + let indirectExportEntries = module.indirectExportEntries; + for (let i = 0; i < indirectExportEntries.length; i++) { + let e = indirectExportEntries[i]; + let resolution = module.resolveExport(e.exportName); + if (resolution === null) + ThrowSyntaxError(JSMSG_MISSING_INDIRECT_EXPORT); + if (resolution === "ambiguous") + ThrowSyntaxError(JSMSG_AMBIGUOUS_INDIRECT_EXPORT); + } + + // Step 12 + let importEntries = module.importEntries; + for (let i = 0; i < importEntries.length; i++) { + let imp = importEntries[i]; + let importedModule = HostResolveImportedModule(module, imp.moduleRequest); + if (imp.importName === "*") { + // TODO + // let namespace = GetModuleNamespace(importedModule); + // CreateNamespaceBinding(module.environment, imp.localName, namespace); + } else { + let resolution = importedModule.resolveExport(imp.importName); + if (resolution === null) + ThrowSyntaxError(JSMSG_MISSING_IMPORT); + if (resolution === "ambiguous") + ThrowSyntaxError(JSMSG_AMBIGUOUS_IMPORT); + CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); + } + } +} + +// 15.2.1.16.5 ModuleEvaluation() +function ModuleEvaluation() +{ + if (!IsObject(this) || !IsModule(this)) + return callFunction(CallModuleMethodIfWrapped, this, "ModuleEvaluation"); + + // Step 1 + let module = this; + + // Step 4 + if (module.evaluated) + return undefined; + + // Step 5 + SetModuleEvaluated(this); + + // Step 6 + let requestedModules = module.requestedModules; + for (let i = 0; i < requestedModules.length; i++) { + let required = requestedModules[i]; + let requiredModule = HostResolveImportedModule(module, required); + requiredModule.evaluation(); + } + + return EvaluateModule(module); +}
--- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -207,33 +207,40 @@ ExportEntryObject::create(JSContext* cx, self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName)); self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest)); self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName)); self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName)); return self; } /////////////////////////////////////////////////////////////////////////// +// IndirectBinding + +IndirectBinding::IndirectBinding(Handle<ModuleEnvironmentObject*> environment, HandleId localName) + : environment(environment), localName(localName) +{} + +/////////////////////////////////////////////////////////////////////////// // ModuleObject /* static */ const Class ModuleObject::class_ = { "Module", JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) | JSCLASS_HAS_CACHED_PROTO(JSProto_Module) | JSCLASS_IS_ANONYMOUS, nullptr, /* addProperty */ nullptr, /* delProperty */ nullptr, /* getProperty */ nullptr, /* setProperty */ nullptr, /* enumerate */ nullptr, /* resolve */ nullptr, /* mayResolve */ nullptr, /* convert */ - nullptr, /* finalize */ + ModuleObject::finalize, nullptr, /* call */ nullptr, /* hasInstance */ nullptr, /* construct */ ModuleObject::trace }; #define DEFINE_ARRAY_SLOT_ACCESSOR(cls, name, slot) \ ArrayObject& \ @@ -252,24 +259,57 @@ DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, ModuleObject::isInstance(HandleValue value) { return value.isObject() && value.toObject().is<ModuleObject>(); } /* static */ ModuleObject* ModuleObject::create(ExclusiveContext* cx) { - return NewBuiltinClassInstance<ModuleObject>(cx, TenuredObject); + Rooted<ModuleObject*> self(cx, NewBuiltinClassInstance<ModuleObject>(cx, TenuredObject)); + + IndirectBindingMap* bindings = cx->new_<IndirectBindingMap>(); + if (!bindings || !bindings->init()) { + ReportOutOfMemory(cx); + return nullptr; + } + + self->setReservedSlot(ImportBindingsSlot, PrivateValue(bindings)); + + return self; +} + +/* static */ void +ModuleObject::finalize(js::FreeOp* fop, JSObject* obj) +{ + fop->delete_(&obj->as<ModuleObject>().importBindings()); +} + +ModuleEnvironmentObject* +ModuleObject::environment() const +{ + Value value = getReservedSlot(EnvironmentSlot); + if (value.isUndefined()) + return nullptr; + + return &value.toObject().as<ModuleEnvironmentObject>(); +} + +IndirectBindingMap& +ModuleObject::importBindings() +{ + return *static_cast<IndirectBindingMap*>(getReservedSlot(ImportBindingsSlot).toPrivate()); } void ModuleObject::init(HandleScript script) { MOZ_ASSERT(!script->enclosingStaticScope()); initReservedSlot(ScriptSlot, PrivateValue(script)); + initReservedSlot(EvaluatedSlot, BooleanValue(false)); } void ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment) { initReservedSlot(InitialEnvironmentSlot, ObjectValue(*initialEnvironment)); } @@ -296,16 +336,22 @@ ModuleObject::hasScript() const } JSScript* ModuleObject::script() const { return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate()); } +bool +ModuleObject::evaluated() const +{ + return getReservedSlot(EvaluatedSlot).toBoolean(); +} + ModuleEnvironmentObject& ModuleObject::initialEnvironment() const { return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>(); } JSObject* ModuleObject::enclosingStaticScope() const @@ -320,45 +366,90 @@ ModuleObject::enclosingStaticScope() con ModuleObject::trace(JSTracer* trc, JSObject* obj) { ModuleObject& module = obj->as<ModuleObject>(); if (module.hasScript()) { JSScript* script = module.script(); TraceManuallyBarrieredEdge(trc, &script, "Module script"); module.setReservedSlot(ScriptSlot, PrivateValue(script)); } + + IndirectBindingMap& bindings = module.importBindings(); + for (IndirectBindingMap::Enum e(bindings); !e.empty(); e.popFront()) { + IndirectBinding& b = e.front().value(); + TraceEdge(trc, &b.environment, "module import environment"); + TraceEdge(trc, &b.localName, "module import local name"); + jsid bindingName = e.front().key(); + TraceManuallyBarrieredEdge(trc, &bindingName, "module import binding name"); + MOZ_ASSERT(bindingName == e.front().key()); + } +} + +void +ModuleObject::createEnvironment() +{ + // The environment has already been created, we just neet to set it in the + // right slot. + MOZ_ASSERT(!getReservedSlot(InitialEnvironmentSlot).isUndefined()); + MOZ_ASSERT(getReservedSlot(EnvironmentSlot).isUndefined()); + setReservedSlot(EnvironmentSlot, getReservedSlot(InitialEnvironmentSlot)); +} + +void +ModuleObject::setEvaluated() +{ + MOZ_ASSERT(!evaluated()); + setReservedSlot(EvaluatedSlot, TrueHandleValue); +} + +bool +ModuleObject::evaluate(JSContext* cx, MutableHandleValue rval) +{ + RootedScript script(cx, this->script()); + return JS_ExecuteScript(cx, script, rval); } DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot) +DEFINE_GETTER_FUNCTIONS(ModuleObject, environment, EnvironmentSlot) +DEFINE_GETTER_FUNCTIONS(ModuleObject, evaluated, EvaluatedSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot) DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot) JSObject* js::InitModuleClass(JSContext* cx, HandleObject obj) { static const JSPropertySpec protoAccessors[] = { JS_PSG("initialEnvironment", ModuleObject_initialEnvironmentGetter, 0), + JS_PSG("environment", ModuleObject_environmentGetter, 0), + JS_PSG("evaluated", ModuleObject_evaluatedGetter, 0), JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0), JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0), JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0), JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0), JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0), JS_PS_END }; + static const JSFunctionSpec protoFunctions[] = { + JS_SELF_HOSTED_FN("resolveExport", "ModuleResolveExport", 3, 0), + JS_SELF_HOSTED_FN("declarationInstantiation", "ModuleDeclarationInstantiation", 0, 0), + JS_SELF_HOSTED_FN("evaluation", "ModuleEvaluation", 0, 0), + JS_FS_END + }; + Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>()); RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx)); if (!proto) return nullptr; - if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr)) + if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, protoFunctions)) return nullptr; global->setPrototype(JSProto_Module, ObjectValue(*proto)); return proto; } #undef DEFINE_GETTER_FUNCTIONS #undef DEFINE_STRING_ACCESSOR_METHOD
--- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -3,16 +3,17 @@ * 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 builtin_ModuleObject_h #define builtin_ModuleObject_h #include "jsapi.h" +#include "jsatom.h" #include "js/TraceableVector.h" #include "vm/NativeObject.h" namespace js { class ModuleEnvironmentObject; @@ -65,28 +66,40 @@ class ExportEntryObject : public NativeO HandleAtom maybeImportName, HandleAtom maybeLocalName); JSAtom* exportName(); JSAtom* moduleRequest(); JSAtom* importName(); JSAtom* localName(); }; +struct IndirectBinding +{ + IndirectBinding(Handle<ModuleEnvironmentObject*> environment, HandleId localName); + RelocatablePtr<ModuleEnvironmentObject*> environment; + RelocatableId localName; +}; + +typedef HashMap<jsid, IndirectBinding, JsidHasher, SystemAllocPolicy> IndirectBindingMap; + class ModuleObject : public NativeObject { public: enum { ScriptSlot = 0, InitialEnvironmentSlot, + EnvironmentSlot, + EvaluatedSlot, RequestedModulesSlot, ImportEntriesSlot, LocalExportEntriesSlot, IndirectExportEntriesSlot, StarExportEntriesSlot, + ImportBindingsSlot, SlotCount }; static const Class class_; static bool isInstance(HandleValue value); static ModuleObject* create(ExclusiveContext* cx); @@ -95,25 +108,34 @@ class ModuleObject : public NativeObject void initImportExportData(HandleArrayObject requestedModules, HandleArrayObject importEntries, HandleArrayObject localExportEntries, HandleArrayObject indiretExportEntries, HandleArrayObject starExportEntries); JSScript* script() const; ModuleEnvironmentObject& initialEnvironment() const; + ModuleEnvironmentObject* environment() const; + bool evaluated() const; ArrayObject& requestedModules() const; ArrayObject& importEntries() const; ArrayObject& localExportEntries() const; ArrayObject& indirectExportEntries() const; ArrayObject& starExportEntries() const; JSObject* enclosingStaticScope() const; + IndirectBindingMap& importBindings(); + + void createEnvironment(); + + void setEvaluated(); + bool evaluate(JSContext*cx, MutableHandleValue rval); private: static void trace(JSTracer* trc, JSObject* obj); + static void finalize(js::FreeOp* fop, JSObject* obj); bool hasScript() const; }; typedef Rooted<ModuleObject*> RootedModuleObject; typedef Handle<ModuleObject*> HandleModuleObject; // Process a module's parse tree to collate the import and export data used when @@ -130,16 +152,17 @@ class MOZ_STACK_CLASS ModuleBuilder using RootedAtomVector = JS::Rooted<AtomVector>; using ImportEntryVector = TraceableVector<ImportEntryObject*>; using RootedImportEntryVector = JS::Rooted<ImportEntryVector>; using ExportEntryVector = TraceableVector<ExportEntryObject*> ; using RootedExportEntryVector = JS::Rooted<ExportEntryVector> ; JSContext* cx_; RootedAtomVector requestedModules_; + RootedAtomVector importedBoundNames_; RootedImportEntryVector importEntries_; RootedExportEntryVector exportEntries_; RootedExportEntryVector localExportEntries_; RootedExportEntryVector indirectExportEntries_; RootedExportEntryVector starExportEntries_; bool processImport(frontend::ParseNode* pn);
--- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2122,33 +2122,33 @@ class BackEdge { BackEdge& operator=(const BackEdge&) = delete; }; // A path-finding handler class for use with JS::ubi::BreadthFirst. struct FindPathHandler { typedef BackEdge NodeData; typedef JS::ubi::BreadthFirst<FindPathHandler> Traversal; - FindPathHandler(JS::ubi::Node start, JS::ubi::Node target, + FindPathHandler(JSContext*cx, JS::ubi::Node start, JS::ubi::Node target, AutoValueVector& nodes, Vector<EdgeName>& edges) - : start(start), target(target), foundPath(false), + : cx(cx), start(start), target(target), foundPath(false), nodes(nodes), edges(edges) { } bool operator()(Traversal& traversal, JS::ubi::Node origin, const JS::ubi::Edge& edge, BackEdge* backEdge, bool first) { // We take care of each node the first time we visit it, so there's // nothing to be done on subsequent visits. if (!first) return true; // Record how we reached this node. This is the last edge on a // shortest path to this node. - EdgeName edgeName = DuplicateString(traversal.cx, edge.name); + EdgeName edgeName = DuplicateString(cx, edge.name); if (!edgeName) return false; *backEdge = mozilla::Move(BackEdge(origin, Move(edgeName))); // Have we reached our final target node? if (edge.referent == target) { // Record the path that got us here, which must be a shortest path. if (!recordPath(traversal)) @@ -2175,16 +2175,18 @@ struct FindPathHandler { !edges.append(p->value().forgetName())) return false; here = predecessor; } while (here != start); return true; } + JSContext* cx; + // The node we're starting from. JS::ubi::Node start; // The node we're looking for. JS::ubi::Node target; // True if we found a path to target, false if we didn't. bool foundPath; @@ -2233,18 +2235,18 @@ FindPath(JSContext* cx, unsigned argc, V { // We can't tolerate the GC moving things around while we're searching // the heap. Check that nothing we do causes a GC. JS::AutoCheckCannotGC autoCannotGC; JS::ubi::Node start(args[0]), target(args[1]); - heaptools::FindPathHandler handler(start, target, nodes, edges); - heaptools::FindPathHandler::Traversal traversal(cx, handler, autoCannotGC); + heaptools::FindPathHandler handler(cx, start, target, nodes, edges); + heaptools::FindPathHandler::Traversal traversal(cx->runtime(), handler, autoCannotGC); if (!traversal.init() || !traversal.addStart(start)) return false; if (!traversal.traverse()) return false; if (!handler.foundPath) { // We didn't find any paths from the start to the target.
--- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -668,36 +668,38 @@ class GCRuntime void requestMinorGC(JS::gcreason::Reason reason); #ifdef DEBUG bool onBackgroundThread() { return helperState.onBackgroundThread(); } bool currentThreadOwnsGCLock() { - return lockOwner == PR_GetCurrentThread(); + return lockOwner.value == PR_GetCurrentThread(); } #endif // DEBUG void assertCanLock() { MOZ_ASSERT(!currentThreadOwnsGCLock()); } void lockGC() { PR_Lock(lock); - MOZ_ASSERT(!lockOwner); #ifdef DEBUG - lockOwner = PR_GetCurrentThread(); + MOZ_ASSERT(!lockOwner.value); + lockOwner.value = PR_GetCurrentThread(); #endif } void unlockGC() { - MOZ_ASSERT(lockOwner == PR_GetCurrentThread()); - lockOwner = nullptr; +#ifdef DEBUG + MOZ_ASSERT(lockOwner.value == PR_GetCurrentThread()); + lockOwner.value = nullptr; +#endif PR_Unlock(lock); } #ifdef DEBUG bool isAllocAllowed() { return noGCOrAllocationCheck == 0; } void disallowAlloc() { ++noGCOrAllocationCheck; } void allowAlloc() { MOZ_ASSERT(!isAllocAllowed()); @@ -1293,17 +1295,17 @@ class GCRuntime */ int inUnsafeRegion; size_t noGCOrAllocationCheck; #endif /* Synchronize GC heap access between main thread and GCHelperState. */ PRLock* lock; - mozilla::DebugOnly<PRThread*> lockOwner; + mozilla::DebugOnly<mozilla::Atomic<PRThread*>> lockOwner; BackgroundAllocTask allocTask; GCHelperState helperState; /* * During incremental sweeping, this field temporarily holds the arenas of * the current AllocKind being swept in order of increasing free space. */
--- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -365,17 +365,18 @@ AssertRootMarkingPhase(JSTracer* trc) D(ArgumentsObject*) \ D(ArrayBufferObject*) \ D(ArrayBufferObjectMaybeShared*) \ D(ArrayBufferViewObject*) \ D(DebugScopeObject*) \ D(GlobalObject*) \ D(JSObject*) \ D(JSFunction*) \ - D(ModuleObject*) \ + D(ModuleObject*) \ + D(ModuleEnvironmentObject*) \ D(NestedScopeObject*) \ D(PlainObject*) \ D(SavedFrame*) \ D(ScopeObject*) \ D(ScriptSourceObject*) \ D(SharedArrayBufferObject*) \ D(SharedTypedArrayObject*) \ D(ImportEntryObject*) \
--- a/js/src/irregexp/NativeRegExpMacroAssembler.cpp +++ b/js/src/irregexp/NativeRegExpMacroAssembler.cpp @@ -128,16 +128,24 @@ NativeRegExpMacroAssembler::GenerateCode // Push non-volatile registers which might be modified by jitcode. size_t pushedNonVolatileRegisters = 0; for (GeneralRegisterForwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter) { masm.Push(*iter); pushedNonVolatileRegisters++; } +#if defined(XP_IOS) && defined(JS_CODEGEN_ARM) + // The stack is 4-byte aligned on iOS, force 8-byte alignment. + masm.movePtr(StackPointer, temp0); + masm.andPtr(Imm32(~7), StackPointer); + masm.push(temp0); + masm.push(temp0); +#endif + #ifndef JS_CODEGEN_X86 // The InputOutputData* is stored as an argument, save it on the stack // above the frame. masm.Push(IntArgReg0); #endif size_t frameSize = sizeof(FrameData) + num_registers_ * sizeof(void*); frameSize = JS_ROUNDUP(frameSize + masm.framePushed(), ABIStackAlignment) - masm.framePushed(); @@ -364,16 +372,21 @@ NativeRegExpMacroAssembler::GenerateCode #ifndef JS_CODEGEN_X86 // Include the InputOutputData* when adjusting the stack size. masm.freeStack(frameSize + sizeof(void*)); #else masm.freeStack(frameSize); #endif +#if defined(XP_IOS) && defined(JS_CODEGEN_ARM) + masm.pop(temp0); + masm.movePtr(temp0, StackPointer); +#endif + // Restore non-volatile registers which were saved on entry. for (GeneralRegisterBackwardIterator iter(savedNonVolatileRegisters); iter.more(); ++iter) masm.Pop(*iter); masm.abiret(); // Backtrack code (branch target for conditional backtracks). if (backtrack_label_.used()) {
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1207413.js @@ -0,0 +1,15 @@ +if (typeof oomAfterAllocations !== 'function') + quit(); + +function first(a) { + return a[0]; +} + +try { + first([function() {}]); + first([function() {}]); + oomAfterAllocations(50); + first([function() {}]); +} catch(e) { + // ignore oom +}
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/modules/ambiguous-star-export.js @@ -0,0 +1,26 @@ +// Test ambigious export * statements. + +"use strict"; + +load(libdir + "asserts.js"); + +let moduleRepo = new Map(); +setModuleResolveHook(function(module, specifier) { + if (specifier in moduleRepo) + return moduleRepo[specifier]; + throw "Module " + specifier + " not found"; +}); + +let a = moduleRepo['a'] = parseModule("export var a = 1; export var b = 2;"); +let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;"); +let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';"); +let ms = [a, b, c]; +ms.map((m) => m.declarationInstantiation()); +ms.map((m) => m.evaluation(), moduleRepo.values()); + +let d = moduleRepo['d'] = parseModule("import { a } from 'c'; a;"); +d.declarationInstantiation(); +assertEq(d.evaluation(), 1); + +let e = moduleRepo['e'] = parseModule("import { b } from 'c';"); +assertThrowsInstanceOf(() => e.declarationInstantiation(), SyntaxError);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/modules/module-declaration-instantiation.js @@ -0,0 +1,35 @@ +// Exercise ModuleDeclarationInstantiation() operation. + +function parseAndInstantiate(source) { + let m = parseModule(source); + m.declarationInstantiation(); + return m.environment; +} + +function testModuleEnvironment(module, expected) { + var actual = Object.keys(module.environment); + assertEq(actual.length, expected.length); + for (var i = 0; i < actual.length; i++) { + assertEq(actual[i], expected[i]); + } +} + +// Check the environment of an empty module. +let e = parseAndInstantiate(""); +assertEq(Object.keys(e).length, 0); + +let moduleRepo = new Map(); +setModuleResolveHook(function(module, specifier) { + if (specifier in moduleRepo) + return moduleRepo[specifier]; + throw "Module " + specifier + " not found"; +}); + +a = moduleRepo['a'] = parseModule("var x = 1; export { x };"); +b = moduleRepo['b'] = parseModule("import { x as y } from 'a';"); + +a.declarationInstantiation(); +b.declarationInstantiation(); + +testModuleEnvironment(a, ['x']); +testModuleEnvironment(b, ['y']);
new file mode 100644 --- /dev/null +++ b/js/src/jit-test/tests/modules/module-evaluation.js @@ -0,0 +1,99 @@ +// Exercise ModuleEvaluation() concrete method. + +load(libdir + "asserts.js"); + +let moduleRepo = new Map(); +setModuleResolveHook(function(module, specifier) { + if (specifier in moduleRepo) + return moduleRepo[specifier]; + throw "Module " + specifier + " not found"; +}); + +function parseAndEvaluate(source) { + let m = parseModule(source); + m.declarationInstantiation(); + return m.evaluation(); +} + +// Check the evaluation of an empty module succeeds. +assertEq(typeof parseAndEvaluate(""), "undefined"); + +// Check evaluation returns evaluation result the first time, then undefined. +let m = parseModule("1"); +m.declarationInstantiation(); +assertEq(m.evaluation(), 1); +assertEq(typeof m.evaluation(), "undefined"); + +// Check top level variables are initialized by evaluation. +m = parseModule("export var x = 2 + 2;"); +assertEq(typeof m.initialEnvironment.x, "undefined"); +m.declarationInstantiation(); +m.evaluation(); +assertEq(m.environment.x, 4); + +m = parseModule("export let x = 2 * 3;"); +m.declarationInstantiation(); +m.evaluation(); +assertEq(m.environment.x, 6); + +// Set up a module to import from. +let a = moduleRepo['a'] = +parseModule(`var x = 1; + export { x }; + export default 2; + export function f(x) { return x + 1; }`); + +// Check we can evaluate top level definitions. +parseAndEvaluate("var foo = 1;"); +parseAndEvaluate("let foo = 1;"); +parseAndEvaluate("const foo = 1"); +parseAndEvaluate("function foo() {}"); +parseAndEvaluate("class foo { constructor() {} }"); + +// Check we can evaluate all module-related syntax. +parseAndEvaluate("export var foo = 1;"); +parseAndEvaluate("export let foo = 1;"); +parseAndEvaluate("export const foo = 1;"); +parseAndEvaluate("var x = 1; export { x };"); +parseAndEvaluate("export default 1"); +parseAndEvaluate("export default class { constructor() {} };"); +parseAndEvaluate("export default function() {};"); +parseAndEvaluate("export default class foo { constructor() {} };"); +parseAndEvaluate("export default function foo() {};"); +parseAndEvaluate("import a from 'a';"); +parseAndEvaluate("import { x } from 'a';"); +parseAndEvaluate("import * as ns from 'a';"); +parseAndEvaluate("export * from 'a'"); + +// Test default import +m = parseModule("import a from 'a'; a;") +m.declarationInstantiation(); +assertEq(m.evaluation(), 2); + +// Test named import +m = parseModule("import { x as y } from 'a'; y;") +m.declarationInstantiation(); +assertEq(m.evaluation(), 1); + +// Call exported function +m = parseModule("import { f } from 'a'; f(3);") +m.declarationInstantiation(); +assertEq(m.evaluation(), 4); + +// Test importing an indirect export +moduleRepo['b'] = parseModule("export { x as z } from 'a';"); +assertEq(parseAndEvaluate("import { z } from 'b'; z"), 1); + +// Test cyclic dependencies +moduleRepo['c1'] = parseModule("export var x = 1; export {y} from 'c2'"); +moduleRepo['c2'] = parseModule("export var y = 2; export {x} from 'c1'"); +assertDeepEq(parseAndEvaluate(`import { x as x1, y as y1 } from 'c1'; + import { x as x2, y as y2 } from 'c2'; + [x1, y1, x2, y2]`), + [1, 2, 1, 2]); + +// Import access in functions +m = parseModule("import { x } from 'a'; function f() { return x; }") +m.declarationInstantiation(); +m.evaluation(); +assertEq(m.environment.f(), 1);
--- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -667,16 +667,21 @@ BaselineCompiler::initScopeChain() prepareVMCall(); masm.loadBaselineFramePtr(BaselineFrameReg, R0.scratchReg()); pushArg(R0.scratchReg()); if (!callVMNonOp(InitFunctionScopeObjectsInfo, phase)) return false; } + } else if (module()) { + // Modules use a pre-created scope object. + Register scope = R1.scratchReg(); + masm.movePtr(ImmGCPtr(&module()->initialEnvironment()), scope); + masm.storePtr(scope, frame.addressOfScopeChain()); } else { // ScopeChain pointer in BaselineFrame has already been initialized // in prologue. if (script->isForEval() && script->strict()) { // Strict eval needs its own call object. prepareVMCall();
--- a/js/src/jit/BytecodeAnalysis.cpp +++ b/js/src/jit/BytecodeAnalysis.cpp @@ -43,17 +43,18 @@ struct CatchFinallyRange bool BytecodeAnalysis::init(TempAllocator& alloc, GSNCache& gsn) { if (!infos_.growByUninitialized(script_->length())) return false; // Initialize the scope chain slot if either the function needs a CallObject // or the script uses the scope chain. The latter case is handled below. - usesScopeChain_ = (script_->functionDelazifying() && + usesScopeChain_ = script_->module() || + (script_->functionDelazifying() && script_->functionDelazifying()->needsCallObject()); MOZ_ASSERT_IF(script_->hasAnyAliasedBindings(), usesScopeChain_); jsbytecode* end = script_->codeEnd(); // Clear all BytecodeInfo. mozilla::PodZero(infos_.begin(), infos_.length()); infos_[0].init(/*stackDepth=*/0);
--- a/js/src/jit/CompileInfo.h +++ b/js/src/jit/CompileInfo.h @@ -242,16 +242,19 @@ class CompileInfo return script_; } bool compilingAsmJS() const { return script() == nullptr; } JSFunction* funMaybeLazy() const { return fun_; } + ModuleObject* module() const { + return script_->module(); + } bool constructing() const { return constructing_; } jsbytecode* osrPc() { return osrPc_; } NestedScopeObject* osrStaticScope() const { return osrStaticScope_;
--- a/js/src/jit/ExecutableAllocator.h +++ b/js/src/jit/ExecutableAllocator.h @@ -54,16 +54,20 @@ static void sync_instruction_memory(cadd extern "C" void sync_instruction_memory(caddr_t v, u_int len); #endif #endif #if defined(JS_CODEGEN_MIPS32) && defined(__linux__) && !defined(JS_SIMULATOR_MIPS32) #include <sys/cachectl.h> #endif +#if defined(JS_CODEGEN_ARM) && defined(XP_IOS) +#include <libkern/OSCacheControl.h> +#endif + namespace JS { struct CodeSizes; } // namespace JS namespace js { namespace jit { enum CodeKind { ION_CODE = 0, BASELINE_CODE, REGEXP_CODE, OTHER_CODE }; @@ -404,16 +408,21 @@ class ExecutableAllocator _flush_cache(reinterpret_cast<char*>(code), size, BCACHE); #endif } #elif defined(JS_CODEGEN_ARM) && (defined(__FreeBSD__) || defined(__NetBSD__)) static void cacheFlush(void* code, size_t size) { __clear_cache(code, reinterpret_cast<char*>(code) + size); } +#elif defined(JS_CODEGEN_ARM) && defined(XP_IOS) + static void cacheFlush(void* code, size_t size) + { + sys_icache_invalidate(code, size); + } #elif defined(JS_CODEGEN_ARM) && (defined(__linux__) || defined(ANDROID)) && defined(__GNUC__) static void cacheFlush(void* code, size_t size) { asm volatile ( "push {r7}\n" "mov r0, %0\n" "mov r1, %1\n" "mov r7, #0xf0000\n"
--- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1224,16 +1224,19 @@ IonBuilder::initScopeChain(MDefinition* if (!scope) return false; } scope = createCallObject(callee, scope); if (!scope) return false; } + } else if (ModuleObject* module = info().module()) { + // Modules use a pre-created scope object. + scope = constant(ObjectValue(module->initialEnvironment())); } else { // For global scripts without a non-syntactic global scope, the scope // chain is the global object. MOZ_ASSERT(!script()->isForEval()); MOZ_ASSERT(!script()->hasNonSyntacticScope()); scope = constant(ObjectValue(script()->global())); } @@ -9011,16 +9014,18 @@ IonBuilder::computeHeapType(const Tempor HeapTypeSetKey property = key->property(id); HeapTypeSet* currentSet = property.maybeTypes(); if (!currentSet || currentSet->unknown()) return nullptr; properties.infallibleAppend(property); acc = TypeSet::unionSets(acc, currentSet, lifoAlloc); + if (!acc) + return nullptr; } // Freeze all the properties associated with the refined type set. for (HeapTypeSetKey* i = properties.begin(); i != properties.end(); i++) i->freeze(constraints()); return acc; }
--- a/js/src/jit/IonCaches.cpp +++ b/js/src/jit/IonCaches.cpp @@ -4686,16 +4686,19 @@ IsCacheableNameReadSlot(HandleObject sco if (!obj->isNative()) return false; if (obj->is<GlobalObject>()) { // Support only simple property lookups. if (!IsCacheableGetPropReadSlotForIon(obj, holder, shape) && !IsCacheableNoProperty(obj, holder, shape, pc, output)) return false; + } else if (obj->is<ModuleEnvironmentObject>()) { + // We don't yet support lookups in a module environment. + return false; } else if (obj->is<CallObject>()) { MOZ_ASSERT(obj == holder); if (!shape->hasDefaultGetter()) return false; } else { // We don't yet support lookups on Block or DeclEnv objects. return false; }
--- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -1856,18 +1856,21 @@ jit::MergeTypes(MIRType* ptype, Temporar if (*ptypeSet) { LifoAlloc* alloc = GetJitContext()->temp->lifoAlloc(); if (!newTypeSet && newType != MIRType_Value) { newTypeSet = MakeMIRTypeSet(newType); if (!newTypeSet) return false; } if (newTypeSet) { - if (!newTypeSet->isSubset(*ptypeSet)) + if (!newTypeSet->isSubset(*ptypeSet)) { *ptypeSet = TypeSet::unionSets(*ptypeSet, newTypeSet, alloc); + if (!*ptypeSet) + return false; + } } else { *ptypeSet = nullptr; } } return true; } // Tests whether 'types' includes all possible values represented by
--- a/js/src/jit/arm/Architecture-arm.cpp +++ b/js/src/jit/arm/Architecture-arm.cpp @@ -1,17 +1,17 @@ /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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 "jit/arm/Architecture-arm.h" -#ifndef JS_SIMULATOR_ARM +#if !defined(JS_ARM_SIMULATOR) && !defined(__APPLE__) #include <elf.h> #endif #include <fcntl.h> #include <unistd.h> #include "jit/arm/Assembler-arm.h" #include "jit/RegisterSets.h" @@ -240,16 +240,25 @@ InitARMFlags() flags |= HWCAP_VFP; #endif #if defined(__ARM_ARCH_7__) || defined (__ARM_ARCH_7A__) // Compiled to use ARMv7 instructions so assume the ARMv7 arch. flags |= HWCAP_ARMv7; #endif +#if defined(__APPLE__) + #if defined(__ARM_NEON__) + flags |= HWCAP_NEON; + #endif + #if defined(__ARMVFPV3__) + flags |= HWCAP_VFPv3 | HWCAP_VFPD32 + #endif +#endif + #endif // JS_SIMULATOR_ARM armHwCapFlags = CanonicalizeARMHwCapFlags(flags); JitSpew(JitSpew_Codegen, "ARM HWCAP: 0x%x\n", armHwCapFlags); return; }
--- a/js/src/jit/arm/Architecture-arm.h +++ b/js/src/jit/arm/Architecture-arm.h @@ -10,18 +10,19 @@ #include "mozilla/MathAlgorithms.h" #include <limits.h> #include <stdint.h> #include "js/Utility.h" // GCC versions 4.6 and above define __ARM_PCS_VFP to denote a hard-float -// ABI target. -#if defined(__ARM_PCS_VFP) +// ABI target. The iOS toolchain doesn't define anything specific here, +// but iOS always supports VFP. +#if defined(__ARM_PCS_VFP) || defined(XP_IOS) #define JS_CODEGEN_ARM_HARDFP #endif namespace js { namespace jit { // In bytes: slots needed for potential memory->memory move spills. // +8 for cycles @@ -104,25 +105,32 @@ class Registers static const SetType AllMask = (1 << Total) - 1; static const SetType ArgRegMask = (1 << r0) | (1 << r1) | (1 << r2) | (1 << r3); static const SetType VolatileMask = (1 << r0) | (1 << r1) | (1 << Registers::r2) | - (1 << Registers::r3); + (1 << Registers::r3) +#if defined(XP_IOS) + // per https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARMv6FunctionCallingConventions.html#//apple_ref/doc/uid/TP40009021-SW4 + | (1 << Registers::r9) +#endif + ; static const SetType NonVolatileMask = (1 << Registers::r4) | (1 << Registers::r5) | (1 << Registers::r6) | (1 << Registers::r7) | (1 << Registers::r8) | +#if !defined(XP_IOS) (1 << Registers::r9) | +#endif (1 << Registers::r10) | (1 << Registers::r11) | (1 << Registers::r12) | (1 << Registers::r14); static const SetType WrapperMask = VolatileMask | // = arguments (1 << Registers::r4) | // = outReg
new file mode 100644 --- /dev/null +++ b/js/src/jit/arm/llvm-compiler-rt/arm/aeabi_idivmod.S @@ -0,0 +1,27 @@ +//===-- aeabi_idivmod.S - EABI idivmod implementation ---------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../assembly.h" + +// struct { int quot, int rem} __aeabi_idivmod(int numerator, int denominator) { +// int rem, quot; +// quot = __divmodsi4(numerator, denominator, &rem); +// return {quot, rem}; +// } + + .syntax unified + .align 2 +DEFINE_COMPILERRT_FUNCTION(__aeabi_idivmod) + push { lr } + sub sp, sp, #4 + mov r2, sp + bl SYMBOL_NAME(__divmodsi4) + ldr r1, [sp] + add sp, sp, #4 + pop { pc }
new file mode 100644 --- /dev/null +++ b/js/src/jit/arm/llvm-compiler-rt/arm/aeabi_uidivmod.S @@ -0,0 +1,28 @@ +//===-- aeabi_uidivmod.S - EABI uidivmod implementation -------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../assembly.h" + +// struct { unsigned quot, unsigned rem} +// __aeabi_uidivmod(unsigned numerator, unsigned denominator) { +// unsigned rem, quot; +// quot = __udivmodsi4(numerator, denominator, &rem); +// return {quot, rem}; +// } + + .syntax unified + .align 2 +DEFINE_COMPILERRT_FUNCTION(__aeabi_uidivmod) + push { lr } + sub sp, sp, #4 + mov r2, sp + bl SYMBOL_NAME(__udivmodsi4) + ldr r1, [sp] + add sp, sp, #4 + pop { pc }
new file mode 100644 --- /dev/null +++ b/js/src/jit/arm/llvm-compiler-rt/assembly.h @@ -0,0 +1,70 @@ +/* ===-- assembly.h - compiler-rt assembler support macros -----------------=== + * + * The LLVM Compiler Infrastructure + * + * This file is dual licensed under the MIT and the University of Illinois Open + * Source Licenses. See LICENSE.TXT for details. + * + * ===----------------------------------------------------------------------=== + * + * This file defines macros for use in compiler-rt assembler source. + * This file is not part of the interface of this library. + * + * ===----------------------------------------------------------------------=== + */ + +#ifndef COMPILERRT_ASSEMBLY_H +#define COMPILERRT_ASSEMBLY_H + +#if defined(__POWERPC__) || defined(__powerpc__) || defined(__ppc__) +#define SEPARATOR @ +#else +#define SEPARATOR ; +#endif + +#if defined(__APPLE__) +#define HIDDEN_DIRECTIVE .private_extern +#define LOCAL_LABEL(name) L_##name +#else +#define HIDDEN_DIRECTIVE .hidden +#define LOCAL_LABEL(name) .L_##name +#endif + +#define GLUE2(a, b) a ## b +#define GLUE(a, b) GLUE2(a, b) +#define SYMBOL_NAME(name) GLUE(__USER_LABEL_PREFIX__, name) + +#ifdef VISIBILITY_HIDDEN +#define DECLARE_SYMBOL_VISIBILITY(name) \ + HIDDEN_DIRECTIVE SYMBOL_NAME(name) SEPARATOR +#else +#define DECLARE_SYMBOL_VISIBILITY(name) +#endif + +#define DEFINE_COMPILERRT_FUNCTION(name) \ + .globl SYMBOL_NAME(name) SEPARATOR \ + DECLARE_SYMBOL_VISIBILITY(name) \ + SYMBOL_NAME(name): + +#define DEFINE_COMPILERRT_PRIVATE_FUNCTION(name) \ + .globl SYMBOL_NAME(name) SEPARATOR \ + HIDDEN_DIRECTIVE SYMBOL_NAME(name) SEPARATOR \ + SYMBOL_NAME(name): + +#define DEFINE_COMPILERRT_PRIVATE_FUNCTION_UNMANGLED(name) \ + .globl name SEPARATOR \ + HIDDEN_DIRECTIVE name SEPARATOR \ + name: + +#define DEFINE_COMPILERRT_FUNCTION_ALIAS(name, target) \ + .globl SYMBOL_NAME(name) SEPARATOR \ + .set SYMBOL_NAME(name), SYMBOL_NAME(target) SEPARATOR + +#if defined (__ARM_EABI__) +# define DEFINE_AEABI_FUNCTION_ALIAS(aeabi_name, name) \ + DEFINE_COMPILERRT_FUNCTION_ALIAS(aeabi_name, name) +#else +# define DEFINE_AEABI_FUNCTION_ALIAS(aeabi_name, name) +#endif + +#endif /* COMPILERRT_ASSEMBLY_H */
--- a/js/src/jit/shared/BaselineCompiler-shared.h +++ b/js/src/jit/shared/BaselineCompiler-shared.h @@ -118,16 +118,20 @@ class BaselineCompilerShared } JSFunction* function() const { // Not delazifying here is ok as the function is guaranteed to have // been delazified before compilation started. return script->functionNonDelazifying(); } + ModuleObject* module() const { + return script->module(); + } + PCMappingSlotInfo getStackTopSlotInfo() { MOZ_ASSERT(frame.numUnsyncedSlots() <= 2); switch (frame.numUnsyncedSlots()) { case 0: return PCMappingSlotInfo::MakeSlotInfo(); case 1: return PCMappingSlotInfo::MakeSlotInfo(PCMappingSlotInfo::ToSlotLocation(frame.peek(-1))); case 2:
--- a/js/src/js.msg +++ b/js/src/js.msg @@ -500,8 +500,15 @@ MSG_DEF(JSMSG_CANT_DEFINE_WINDOW_ELEMENT MSG_DEF(JSMSG_CANT_DELETE_WINDOW_ELEMENT, 0, JSEXN_TYPEERR, "can't delete elements from a Window object") MSG_DEF(JSMSG_CANT_DELETE_WINDOW_NAMED_PROPERTY, 1, JSEXN_TYPEERR, "can't delete property {0} from window's named properties object") MSG_DEF(JSMSG_CANT_PREVENT_EXTENSIONS, 0, JSEXN_TYPEERR, "can't prevent extensions on this proxy object") MSG_DEF(JSMSG_NO_NAMED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have a named property setter for '{1}'") MSG_DEF(JSMSG_NO_INDEXED_SETTER, 2, JSEXN_TYPEERR, "{0} doesn't have an indexed property setter for '{1}'") // Super MSG_DEF(JSMSG_CANT_DELETE_SUPER, 0, JSEXN_REFERENCEERR, "invalid delete involving 'super'") + +// Modules +MSG_DEF(JSMSG_BAD_DEFAULT_EXPORT, 0, JSEXN_SYNTAXERR, "default export cannot be provided by export *") +MSG_DEF(JSMSG_MISSING_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "indirect export not found") +MSG_DEF(JSMSG_AMBIGUOUS_INDIRECT_EXPORT, 0, JSEXN_SYNTAXERR, "ambiguous indirect export") +MSG_DEF(JSMSG_MISSING_IMPORT, 0, JSEXN_SYNTAXERR, "import not found") +MSG_DEF(JSMSG_AMBIGUOUS_IMPORT, 0, JSEXN_SYNTAXERR, "ambiguous import")
--- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1171,17 +1171,16 @@ GCRuntime::GCRuntime(JSRuntime* rt) : mallocBytesUntilGC(0), mallocGCTriggered(false), alwaysPreserveCode(false), #ifdef DEBUG inUnsafeRegion(0), noGCOrAllocationCheck(0), #endif lock(nullptr), - lockOwner(nullptr), allocTask(rt, emptyChunks_), helperState(rt) { setGCMode(JSGC_MODE_GLOBAL); } #ifdef JS_GC_ZEAL @@ -3404,21 +3403,21 @@ GCHelperState::startBackgroundThread(Sta } void GCHelperState::waitForBackgroundThread() { MOZ_ASSERT(CurrentThreadCanAccessRuntime(rt)); #ifdef DEBUG - rt->gc.lockOwner = nullptr; + rt->gc.lockOwner.value = nullptr; #endif PR_WaitCondVar(done, PR_INTERVAL_NO_TIMEOUT); #ifdef DEBUG - rt->gc.lockOwner = PR_GetCurrentThread(); + rt->gc.lockOwner.value = PR_GetCurrentThread(); #endif } void GCHelperState::work() { MOZ_ASSERT(CanUseExtraThreads());
--- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -4538,16 +4538,27 @@ js::DuplicateString(js::ExclusiveContext size_t n = js_strlen(s) + 1; auto ret = cx->make_pod_array<char16_t>(n); if (!ret) return ret; PodCopy(ret.get(), s, n); return ret; } +UniquePtr<char16_t[], JS::FreePolicy> +js::DuplicateString(const char16_t* s) +{ + size_t n = js_strlen(s) + 1; + UniquePtr<char16_t[], JS::FreePolicy> ret(js_pod_malloc<char16_t>(n)); + if (!ret) + return nullptr; + PodCopy(ret.get(), s, n); + return ret; +} + template <typename CharT> const CharT* js_strchr_limit(const CharT* s, char16_t c, const CharT* limit) { while (s < limit) { if (*s == c) return s; s++;
--- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -124,16 +124,21 @@ extern const char* ValueToPrintable(JSContext* cx, const Value&, JSAutoByteString* bytes, bool asSource = false); extern mozilla::UniquePtr<char[], JS::FreePolicy> DuplicateString(ExclusiveContext* cx, const char* s); extern mozilla::UniquePtr<char16_t[], JS::FreePolicy> DuplicateString(ExclusiveContext* cx, const char16_t* s); +// This variant does not report OOMs, you must arrange for OOMs to be reported +// yourself. +extern mozilla::UniquePtr<char16_t[], JS::FreePolicy> +DuplicateString(const char16_t* s); + /* * Convert a non-string value to a string, returning null after reporting an * error, otherwise returning a new string reference. */ template <AllowGC allowGC> extern JSString* ToStringSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::HandleType arg);
--- a/js/src/moz.build +++ b/js/src/moz.build @@ -433,16 +433,21 @@ elif CONFIG['JS_CODEGEN_ARM']: 'jit/arm/MoveEmitter-arm.cpp', 'jit/arm/SharedIC-arm.cpp', 'jit/arm/Trampoline-arm.cpp', ] if CONFIG['JS_SIMULATOR_ARM']: UNIFIED_SOURCES += [ 'jit/arm/Simulator-arm.cpp' ] + elif CONFIG['OS_ARCH'] == 'Darwin': + SOURCES += [ + 'jit/arm/llvm-compiler-rt/arm/aeabi_idivmod.S', + 'jit/arm/llvm-compiler-rt/arm/aeabi_uidivmod.S', + ] elif CONFIG['JS_CODEGEN_ARM64']: UNIFIED_SOURCES += [ 'jit/arm64/Architecture-arm64.cpp', 'jit/arm64/Assembler-arm64.cpp', 'jit/arm64/Bailouts-arm64.cpp', 'jit/arm64/BaselineIC-arm64.cpp', 'jit/arm64/CodeGenerator-arm64.cpp', 'jit/arm64/Lowering-arm64.cpp',
--- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -3101,16 +3101,39 @@ ParseModule(JSContext* cx, unsigned argc if (!module) return false; args.rval().setObject(*module); return true; } static bool +SetModuleResolveHook(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + JSMSG_MORE_ARGS_NEEDED, "setModuleResolveHook", "0", "s"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) { + const char* typeName = InformalValueTypeName(args[0]); + JS_ReportError(cx, "expected hook function, got %s", typeName); + return false; + } + + RootedFunction hook(cx, &args[0].toObject().as<JSFunction>()); + Rooted<GlobalObject*> global(cx, cx->global()); + global->setModuleResolveHook(hook); + args.rval().setUndefined(); + return true; +} + +static bool Parse(JSContext* cx, unsigned argc, Value* vp) { using namespace js::frontend; CallArgs args = CallArgsFromVp(argc, vp); if (args.length() < 1) { JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, @@ -4662,16 +4685,22 @@ static const JSFunctionSpecWithHelp shel JS_FN_HELP("compile", Compile, 1, 0, "compile(code)", " Compiles a string to bytecode, potentially throwing."), JS_FN_HELP("parseModule", ParseModule, 1, 0, "parseModule(code)", " Parses source text as a module and returns a Module object."), + JS_FN_HELP("setModuleResolveHook", SetModuleResolveHook, 1, 0, +"setModuleResolveHook(function(module, specifier) {})", +" Set the HostResolveImportedModule hook to |function|.\n" +" This hook is used to look up a previously loaded module object. It should\n" +" be implemented by the module loader."), + JS_FN_HELP("parse", Parse, 1, 0, "parse(code)", " Parses a string, potentially throwing."), JS_FN_HELP("syntaxParse", SyntaxParse, 1, 0, "syntaxParse(code)", " Check the syntax of a string, returning success value"),
--- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -4044,23 +4044,27 @@ class MOZ_STACK_CLASS Debugger::ObjectQu { /* * We can't tolerate the GC moving things around while we're * searching the heap. Check that nothing we do causes a GC. */ Maybe<JS::AutoCheckCannotGC> maybeNoGC; RootedObject dbgObj(cx, dbg->object); - JS::ubi::RootList rootList(cx, maybeNoGC); - if (!rootList.init(dbgObj)) + JS::ubi::RootList rootList(cx->runtime(), maybeNoGC); + if (!rootList.init(dbgObj)) { + ReportOutOfMemory(cx); return false; - - Traversal traversal(cx, *this, maybeNoGC.ref()); - if (!traversal.init()) + } + + Traversal traversal(cx->runtime(), *this, maybeNoGC.ref()); + if (!traversal.init()) { + ReportOutOfMemory(cx); return false; + } traversal.wantNames = false; return traversal.addStart(JS::ubi::Node(&rootList)) && traversal.traverse(); } } /*
--- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -535,28 +535,33 @@ DebuggerMemory::takeCensus(JSContext* cx // Populate our target set of debuggee zones. for (WeakGlobalObjectSet::Range r = dbg->allDebuggees(); !r.empty(); r.popFront()) { if (!census.targetZones.put(r.front()->zone())) return false; } { Maybe<JS::AutoCheckCannotGC> maybeNoGC; - JS::ubi::RootList rootList(cx, maybeNoGC); - if (!rootList.init(dbgObj)) + JS::ubi::RootList rootList(cx->runtime(), maybeNoGC); + if (!rootList.init(dbgObj)) { + ReportOutOfMemory(cx); return false; + } - JS::ubi::CensusTraversal traversal(cx, handler, maybeNoGC.ref()); - if (!traversal.init()) + JS::ubi::CensusTraversal traversal(cx->runtime(), handler, maybeNoGC.ref()); + if (!traversal.init()) { + ReportOutOfMemory(cx); return false; + } traversal.wantNames = false; if (!traversal.addStart(JS::ubi::Node(&rootList)) || !traversal.traverse()) { + ReportOutOfMemory(cx); return false; } } return handler.report(args.rval()); }
--- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -106,16 +106,17 @@ class GlobalObject : public NativeObject DEBUGGERS, INTRINSICS, FLOAT32X4_TYPE_DESCR, FLOAT64X2_TYPE_DESCR, INT8X16_TYPE_DESCR, INT16X8_TYPE_DESCR, INT32X4_TYPE_DESCR, FOR_OF_PIC_CHAIN, + MODULE_RESOLVE_HOOK, /* Total reserved-slot count for global objects. */ RESERVED_SLOTS }; /* * The slot count must be in the public API for JSCLASS_GLOBAL_FLAGS, and * we won't expose GlobalObject, so just assert that the two values are @@ -745,16 +746,29 @@ class GlobalObject : public NativeObject inline NativeObject* getForOfPICObject() { Value forOfPIC = getReservedSlot(FOR_OF_PIC_CHAIN); if (forOfPIC.isUndefined()) return nullptr; return &forOfPIC.toObject().as<NativeObject>(); } static NativeObject* getOrCreateForOfPICObject(JSContext* cx, Handle<GlobalObject*> global); + + void setModuleResolveHook(HandleFunction hook) { + MOZ_ASSERT(hook); + setSlot(MODULE_RESOLVE_HOOK, ObjectValue(*hook)); + } + + JSFunction* moduleResolveHook() { + Value value = getSlotRef(MODULE_RESOLVE_HOOK); + if (value.isUndefined()) + return nullptr; + + return &value.toObject().as<JSFunction>(); + } }; template<> inline void GlobalObject::setCreateArrayFromBuffer<uint8_t>(Handle<JSFunction*> fun) { setCreateArrayFromBufferHelper(FROM_BUFFER_UINT8, fun); }
--- a/js/src/vm/HelperThreads.cpp +++ b/js/src/vm/HelperThreads.cpp @@ -487,19 +487,16 @@ GlobalHelperThreadState::ensureInitializ } GlobalHelperThreadState::GlobalHelperThreadState() : cpuCount(0), threadCount(0), threads(nullptr), asmJSCompilationInProgress(false), helperLock(nullptr), -#ifdef DEBUG - lockOwner(nullptr), -#endif consumerWakeup(nullptr), producerWakeup(nullptr), pauseWakeup(nullptr), numAsmJSFailedJobs(0), asmJSFailedFunction(nullptr) { cpuCount = GetCPUCount(); threadCount = ThreadCountForCPUCount(cpuCount); @@ -540,51 +537,51 @@ GlobalHelperThreadState::finishThreads() void GlobalHelperThreadState::lock() { MOZ_ASSERT(!isLocked()); AssertCurrentThreadCanLock(HelperThreadStateLock); PR_Lock(helperLock); #ifdef DEBUG - lockOwner = PR_GetCurrentThread(); + lockOwner.value = PR_GetCurrentThread(); #endif } void GlobalHelperThreadState::unlock() { MOZ_ASSERT(isLocked()); #ifdef DEBUG - lockOwner = nullptr; + lockOwner.value = nullptr; #endif PR_Unlock(helperLock); } #ifdef DEBUG bool GlobalHelperThreadState::isLocked() { - return lockOwner == PR_GetCurrentThread(); + return lockOwner.value == PR_GetCurrentThread(); } #endif void GlobalHelperThreadState::wait(CondVar which, uint32_t millis) { MOZ_ASSERT(isLocked()); #ifdef DEBUG - lockOwner = nullptr; + lockOwner.value = nullptr; #endif DebugOnly<PRStatus> status = PR_WaitCondVar(whichWakeup(which), millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT); MOZ_ASSERT(status == PR_SUCCESS); #ifdef DEBUG - lockOwner = PR_GetCurrentThread(); + lockOwner.value = PR_GetCurrentThread(); #endif } void GlobalHelperThreadState::notifyAll(CondVar which) { MOZ_ASSERT(isLocked()); PR_NotifyAllCondVar(whichWakeup(which));
--- a/js/src/vm/HelperThreads.h +++ b/js/src/vm/HelperThreads.h @@ -237,19 +237,17 @@ class GlobalHelperThreadState private: /* * Lock protecting all mutable shared state accessed by helper threads, and * used by all condition variables. */ PRLock* helperLock; -#ifdef DEBUG - PRThread* lockOwner; -#endif + mozilla::DebugOnly<mozilla::Atomic<PRThread*>> lockOwner; /* Condvars for threads waiting/notifying each other. */ PRCondVar* consumerWakeup; PRCondVar* producerWakeup; PRCondVar* pauseWakeup; PRCondVar* whichWakeup(CondVar which) { switch (which) {
--- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1008,17 +1008,18 @@ js::Execute(JSContext* cx, HandleScript #endif /* Use the scope chain as 'this', modulo outerization. */ JSObject* thisObj = GetThisObject(cx, scopeChain); if (!thisObj) return false; Value thisv = ObjectValue(*thisObj); - return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), EXECUTE_GLOBAL, + ExecuteType type = script->module() ? EXECUTE_MODULE : EXECUTE_GLOBAL; + return ExecuteKernel(cx, script, *scopeChain, thisv, NullValue(), type, NullFramePtr() /* evalInFrame */, rval); } bool js::HasInstance(JSContext* cx, HandleObject obj, HandleValue v, bool* bp) { const Class* clasp = obj->getClass(); RootedValue local(cx, v);
--- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -7,16 +7,18 @@ #include "vm/ScopeObject-inl.h" #include "mozilla/PodOperations.h" #include "mozilla/SizePrintfMacros.h" #include "jscompartment.h" #include "jsiter.h" +#include "builtin/ModuleObject.h" + #include "vm/ArgumentsObject.h" #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/Shape.h" #include "vm/WeakMapObject.h" #include "vm/Xdr.h" #include "jsatominlines.h" @@ -311,17 +313,45 @@ const Class CallObject::class_ = { JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS) }; /*****************************************************************************/ const Class ModuleEnvironmentObject::class_ = { "ModuleEnvironmentObject", JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) | - JSCLASS_IS_ANONYMOUS + JSCLASS_IS_ANONYMOUS, + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* convert */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + nullptr, /* trace */ + JS_NULL_CLASS_SPEC, + JS_NULL_CLASS_EXT, + { + ModuleEnvironmentObject::lookupProperty, + nullptr, /* defineProperty */ + ModuleEnvironmentObject::hasProperty, + ModuleEnvironmentObject::getProperty, + ModuleEnvironmentObject::setProperty, + ModuleEnvironmentObject::getOwnPropertyDescriptor, + ModuleEnvironmentObject::deleteProperty, + nullptr, nullptr, /* watch/unwatch */ + nullptr, /* getElements */ + ModuleEnvironmentObject::enumerate, + nullptr /* thisObject */ + } }; /* static */ ModuleEnvironmentObject* ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module) { RootedScript script(cx, module->script()); RootedShape shape(cx, script->bindings.callObjShape()); MOZ_ASSERT(shape->getObjectClass() == &class_); @@ -333,17 +363,17 @@ ModuleEnvironmentObject::create(Exclusiv gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots()); MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_)); kind = gc::GetBackgroundAllocKind(kind); JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group); if (!obj) return nullptr; - Rooted<ModuleEnvironmentObject*> scope(cx, &obj->as<ModuleEnvironmentObject>()); + RootedModuleEnvironmentObject scope(cx, &obj->as<ModuleEnvironmentObject>()); // Set uninitialized lexicals even on template objects, as Ion will use // copy over the template object's slot values in the fast path. scope->initAliasedLexicalsToThrowOnTouch(script); scope->initFixedSlot(MODULE_SLOT, ObjectValue(*module)); if (!JSObject::setSingleton(cx, scope)) return nullptr; @@ -351,21 +381,149 @@ ModuleEnvironmentObject::create(Exclusiv // Initialize this early so that we can manipulate the scope object without // causing assertions. scope->setEnclosingScope(cx->global()); return scope; } ModuleObject& -ModuleEnvironmentObject::module() const +ModuleEnvironmentObject::module() { return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>(); } +IndirectBindingMap& +ModuleEnvironmentObject::importBindings() +{ + return module().importBindings(); +} + +bool +ModuleEnvironmentObject::createImportBinding(JSContext*cx, HandleAtom importName, + HandleModuleObject module, HandleAtom exportName) +{ + RootedId importNameId(cx, AtomToId(importName)); + RootedId exportNameId(cx, AtomToId(exportName)); + Rooted<ModuleEnvironmentObject*> env(cx, module->environment()); + +#ifdef DEBUG + bool found = false; + if (!HasProperty(cx, env, exportNameId, &found)) + return false; + MOZ_ASSERT(found); +#endif + + IndirectBinding binding(env, exportNameId); + if (!importBindings().putNew(importNameId, binding)) { + ReportOutOfMemory(cx); + return false; + } + + return true; +} + +/* static */ bool +ModuleEnvironmentObject::lookupProperty(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleObject objp, MutableHandleShape propp) +{ + if (IndirectBindingMap::Ptr p = + obj->as<ModuleEnvironmentObject>().importBindings().lookup(id)) + { + RootedObject target(cx, p->value().environment); + RootedId name(cx, p->value().localName); + return LookupProperty(cx, target, name, objp, propp); + } + + RootedNativeObject target(cx, &obj->as<NativeObject>()); + if (!NativeLookupOwnProperty<CanGC>(cx, target, id, propp)) + return false; + + objp.set(obj); + return true; +} + +/* static */ bool +ModuleEnvironmentObject::hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp) +{ + if (obj->as<ModuleEnvironmentObject>().importBindings().has(id)) { + *foundp = true; + return true; + } + + RootedNativeObject self(cx, &obj->as<NativeObject>()); + return NativeHasProperty(cx, self, id, foundp); +} + +/* static */ bool +ModuleEnvironmentObject::getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, + HandleId id, MutableHandleValue vp) +{ + if (IndirectBindingMap::Ptr p = + obj->as<ModuleEnvironmentObject>().importBindings().lookup(id)) + { + RootedObject target(cx, p->value().environment); + RootedId name(cx, p->value().localName); + return GetProperty(cx, target, target, name, vp); + } + + RootedNativeObject self(cx, &obj->as<NativeObject>()); + return NativeGetProperty(cx, self, receiver, id, vp); +} + +/* static */ bool +ModuleEnvironmentObject::setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, + HandleValue receiver, JS::ObjectOpResult& result) +{ + RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>()); + if (self->importBindings().has(id)) + return result.failReadOnly(); + + return NativeSetProperty(cx, self, id, v, receiver, Qualified, result); +} + +/* static */ bool +ModuleEnvironmentObject::getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, + MutableHandle<JSPropertyDescriptor> desc) +{ + // We never call this hook on scope objects. + MOZ_CRASH(); +} + +/* static */ bool +ModuleEnvironmentObject::deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result) +{ + return result.failCantDelete(); +} + +/* static */ bool +ModuleEnvironmentObject::enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly) +{ + RootedModuleEnvironmentObject self(cx, &obj->as<ModuleEnvironmentObject>()); + const IndirectBindingMap& bs(self->importBindings()); + + MOZ_ASSERT(properties.length() == 0); + size_t count = bs.count() + self->slotSpan() - RESERVED_SLOTS; + if (!properties.reserve(count)) { + ReportOutOfMemory(cx); + return false; + } + + for (auto r = bs.all(); !r.empty(); r.popFront()) + properties.infallibleAppend(r.front().key()); + + for (Shape::Range<NoGC> r(self->lastProperty()); !r.empty(); r.popFront()) + properties.infallibleAppend(r.front().propid()); + + MOZ_ASSERT(properties.length() == count); + return true; +} + /*****************************************************************************/ const Class DeclEnvObject::class_ = { js_Object_str, JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) | JSCLASS_HAS_CACHED_PROTO(JSProto_Object) };
--- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -23,16 +23,19 @@ struct Definition; class FunctionBox; class ModuleBox; } class StaticWithObject; class StaticEvalObject; class StaticNonSyntacticScopeObjects; +class ModuleObject; +typedef Handle<ModuleObject*> HandleModuleObject; + /*****************************************************************************/ /* * The static scope chain is the canonical truth for lexical scope contour of * a program. The dynamic scope chain is derived from the static scope chain: * it is the chain of scopes whose static scopes have a runtime * representation, for example, due to aliased bindings. * @@ -379,17 +382,36 @@ class CallObject : public ScopeObject class ModuleEnvironmentObject : public CallObject { static const uint32_t MODULE_SLOT = CallObject::CALLEE_SLOT; public: static const Class class_; static ModuleEnvironmentObject* create(ExclusiveContext* cx, HandleModuleObject module); - ModuleObject& module() const; + ModuleObject& module(); + IndirectBindingMap& importBindings(); + + bool createImportBinding(JSContext* cx, HandleAtom importName, HandleModuleObject module, + HandleAtom exportName); + + private: + static bool lookupProperty(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleObject objp, MutableHandleShape propp); + static bool hasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp); + static bool getProperty(JSContext* cx, HandleObject obj, HandleValue receiver, HandleId id, + MutableHandleValue vp); + static bool setProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v, + HandleValue receiver, JS::ObjectOpResult& result); + static bool getOwnPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, + MutableHandle<JSPropertyDescriptor> desc); + static bool deleteProperty(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result); + static bool enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly); }; typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject; typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject; typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject; class DeclEnvObject : public ScopeObject {
--- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -16,16 +16,17 @@ #include "jsfriendapi.h" #include "jshashutil.h" #include "jsweakmap.h" #include "jswrapper.h" #include "selfhosted.out.h" #include "builtin/Intl.h" #include "builtin/MapObject.h" +#include "builtin/ModuleObject.h" #include "builtin/Object.h" #include "builtin/Reflect.h" #include "builtin/SelfHostingDefines.h" #include "builtin/SIMD.h" #include "builtin/TypedObject.h" #include "builtin/WeakSetObject.h" #include "gc/Marking.h" #include "jit/InlinableNatives.h" @@ -210,16 +211,26 @@ intrinsic_ThrowTypeError(JSContext* cx, { CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() >= 1); ThrowErrorWithType(cx, JSEXN_TYPEERR, args); return false; } +static bool +intrinsic_ThrowSyntaxError(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() >= 1); + + ThrowErrorWithType(cx, JSEXN_SYNTAXERR, args); + return false; +} + /** * Handles an assertion failure in self-hosted code just like an assertion * failure in C++ code. Information about the failure can be provided in args[0]. */ static bool intrinsic_AssertionFailed(JSContext* cx, unsigned argc, Value* vp) { #ifdef DEBUG @@ -1239,16 +1250,101 @@ intrinsic_ConstructorForTypedArray(JSCon MOZ_ASSERT(protoKey); RootedValue ctor(cx, cx->global()->getConstructor(protoKey)); MOZ_ASSERT(ctor.isObject()); args.rval().set(ctor); return true; } +static bool +intrinsic_IsModule(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + MOZ_ASSERT(args[0].isObject()); + + args.rval().setBoolean(args[0].toObject().is<ModuleObject>()); + return true; +} + +static bool +intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 2); + MOZ_ASSERT(args[0].toObject().is<ModuleObject>()); + MOZ_ASSERT(args[1].isString()); + + RootedFunction moduleResolveHook(cx, cx->global()->moduleResolveHook()); + if (!moduleResolveHook) { + JS_ReportError(cx, "Module resolve hook not set"); + return false; + } + + RootedValue result(cx); + if (!JS_CallFunction(cx, nullptr, moduleResolveHook, args, &result)) + return false; + + if (!result.isObject() || !result.toObject().is<ModuleObject>()) { + JS_ReportError(cx, "Module resolve hook did not return Module object"); + return false; + } + + args.rval().set(result); + return true; +} + +static bool +intrinsic_CreateModuleEnvironment(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>()); + module->createEnvironment(); + args.rval().setUndefined(); + return true; +} + +static bool +intrinsic_CreateImportBinding(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 4); + RootedModuleEnvironmentObject environment(cx, &args[0].toObject().as<ModuleEnvironmentObject>()); + RootedAtom importedName(cx, &args[1].toString()->asAtom()); + RootedModuleObject module(cx, &args[2].toObject().as<ModuleObject>()); + RootedAtom localName(cx, &args[3].toString()->asAtom()); + if (!environment->createImportBinding(cx, importedName, module, localName)) + return false; + + args.rval().setUndefined(); + return true; +} + +static bool +intrinsic_SetModuleEvaluated(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>()); + module->setEvaluated(); + args.rval().setUndefined(); + return true; +} + +static bool +intrinsic_EvaluateModule(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>()); + return module->evaluate(cx, args.rval()); +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these // functions via a content compartment's builtins would be unsafe, because // content script might have changed the builtins' prototypes' members. // Installing the whole set of builtins in the self-hosting compartment, OTOH, // would be wasteful: it increases memory usage and initialization time for // self-hosting compartment. @@ -1324,16 +1420,17 @@ static const JSFunctionSpec intrinsic_fu JS_INLINABLE_FN("ToInteger", intrinsic_ToInteger, 1,0, IntrinsicToInteger), JS_INLINABLE_FN("ToString", intrinsic_ToString, 1,0, IntrinsicToString), JS_FN("ToPropertyKey", intrinsic_ToPropertyKey, 1,0), JS_INLINABLE_FN("IsCallable", intrinsic_IsCallable, 1,0, IntrinsicIsCallable), JS_FN("IsConstructor", intrinsic_IsConstructor, 1,0), JS_FN("OwnPropertyKeys", intrinsic_OwnPropertyKeys, 1,0), JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4,0), JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4,0), + JS_FN("ThrowSyntaxError", intrinsic_ThrowSyntaxError, 4,0), JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1,0), JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2,0), JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,0), JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0), JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0), JS_FN("LocalTZA", intrinsic_LocalTZA, 0,0), JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0, @@ -1480,16 +1577,25 @@ static const JSFunctionSpec intrinsic_fu JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0), JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0), // See builtin/RegExp.h for descriptions of the regexp_* functions. JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0), JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0), JS_FN("regexp_construct_no_statics", regexp_construct_no_statics, 2,0), + JS_FN("IsModule", intrinsic_IsModule, 1, 0), + JS_FN("CallModuleMethodIfWrapped", + CallNonGenericSelfhostedMethod<Is<ModuleObject>>, 2, 0), + JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0), + JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 2, 0), + JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0), + JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0), + JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0), + JS_FS_END }; void js::FillSelfHostingCompileOptions(CompileOptions& options) { /* * In self-hosting mode, scripts use JSOP_GETINTRINSIC instead of
--- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -145,17 +145,17 @@ StackFrame::functionDisplayNameLength() // All operations on null ubi::Nodes crash. CoarseType Concrete<void>::coarseType() const { MOZ_CRASH("null ubi::Node"); } const char16_t* Concrete<void>::typeName() const { MOZ_CRASH("null ubi::Node"); } JS::Zone* Concrete<void>::zone() const { MOZ_CRASH("null ubi::Node"); } JSCompartment* Concrete<void>::compartment() const { MOZ_CRASH("null ubi::Node"); } UniquePtr<EdgeRange> -Concrete<void>::edges(JSContext*, bool) const { +Concrete<void>::edges(JSRuntime*, bool) const { MOZ_CRASH("null ubi::Node"); } Node::Size Concrete<void>::size(mozilla::MallocSizeOf mallocSizeof) const { MOZ_CRASH("null ubi::Node"); } @@ -250,18 +250,18 @@ class EdgeVectorTracer : public JS::Call return; } } public: // True if no errors (OOM, say) have yet occurred. bool okay; - EdgeVectorTracer(JSContext* cx, EdgeVector* vec, bool wantNames) - : JS::CallbackTracer(JS_GetRuntime(cx)), + EdgeVectorTracer(JSRuntime* rt, EdgeVector* vec, bool wantNames) + : JS::CallbackTracer(rt), vec(vec), wantNames(wantNames), okay(true) { } }; // An EdgeRange concrete class that simply holds a vector of Edges, @@ -270,20 +270,20 @@ class SimpleEdgeRange : public EdgeRange EdgeVector edges; size_t i; void settle() { front_ = i < edges.length() ? &edges[i] : nullptr; } public: - explicit SimpleEdgeRange(JSContext* cx) : edges(cx), i(0) { } + explicit SimpleEdgeRange() : edges(), i(0) { } - bool init(JSContext* cx, void* thing, JS::TraceKind kind, bool wantNames = true) { - EdgeVectorTracer tracer(cx, &edges, wantNames); + bool init(JSRuntime* rt, void* thing, JS::TraceKind kind, bool wantNames = true) { + EdgeVectorTracer tracer(rt, &edges, wantNames); js::TraceChildren(&tracer, thing, kind); settle(); return tracer.okay; } void popFront() override { i++; settle(); } }; @@ -292,23 +292,22 @@ template<typename Referent> JS::Zone* TracerConcrete<Referent>::zone() const { return get().zoneFromAnyThread(); } template<typename Referent> UniquePtr<EdgeRange> -TracerConcrete<Referent>::edges(JSContext* cx, bool wantNames) const { - UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range( - cx->new_<SimpleEdgeRange>(cx)); +TracerConcrete<Referent>::edges(JSRuntime* rt, bool wantNames) const { + UniquePtr<SimpleEdgeRange, JS::DeletePolicy<SimpleEdgeRange>> range(js_new<SimpleEdgeRange>()); if (!range) return nullptr; - if (!range->init(cx, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames)) + if (!range->init(rt, ptr, JS::MapTypeToTraceKind<Referent>::kind, wantNames)) return nullptr; return UniquePtr<EdgeRange>(range.release()); } template<typename Referent> JSCompartment* TracerConcreteWithCompartment<Referent>::compartment() const @@ -390,40 +389,40 @@ template class TracerConcreteWithCompart template class TracerConcreteWithCompartment<js::BaseShape>; template class TracerConcrete<js::ObjectGroup>; } // namespace ubi } // namespace JS namespace JS { namespace ubi { -RootList::RootList(JSContext* cx, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */) +RootList::RootList(JSRuntime* rt, Maybe<AutoCheckCannotGC>& noGC, bool wantNames /* = false */) : noGC(noGC), - cx(cx), - edges(cx), + rt(rt), + edges(), wantNames(wantNames) { } bool RootList::init() { - EdgeVectorTracer tracer(cx, &edges, wantNames); + EdgeVectorTracer tracer(rt, &edges, wantNames); JS_TraceRuntime(&tracer); if (!tracer.okay) return false; - noGC.emplace(cx->runtime()); + noGC.emplace(rt); return true; } bool RootList::init(ZoneSet& debuggees) { - EdgeVector allRootEdges(cx); - EdgeVectorTracer tracer(cx, &allRootEdges, wantNames); + EdgeVector allRootEdges; + EdgeVectorTracer tracer(rt, &allRootEdges, wantNames); JS_TraceRuntime(&tracer); if (!tracer.okay) return false; JS_TraceIncomingCCWs(&tracer, debuggees); if (!tracer.okay) return false; @@ -431,17 +430,17 @@ RootList::init(ZoneSet& debuggees) Edge& edge = r.front(); Zone* zone = edge.referent.zone(); if (zone && !debuggees.has(zone)) continue; if (!edges.append(mozilla::Move(edge))) return false; } - noGC.emplace(cx->runtime()); + noGC.emplace(rt); return true; } bool RootList::init(HandleObject debuggees) { MOZ_ASSERT(debuggees && JS::dbg::IsDebugger(*debuggees)); js::Debugger* dbg = js::Debugger::fromJSObject(debuggees.get()); @@ -473,26 +472,26 @@ RootList::init(HandleObject debuggees) bool RootList::addRoot(Node node, const char16_t* edgeName) { MOZ_ASSERT(noGC.isSome()); MOZ_ASSERT_IF(wantNames, edgeName); UniquePtr<char16_t[], JS::FreePolicy> name; if (edgeName) { - name = DuplicateString(cx, edgeName); + name = DuplicateString(edgeName); if (!name) return false; } return edges.append(mozilla::Move(Edge(name.release(), node))); } const char16_t Concrete<RootList>::concreteTypeName[] = MOZ_UTF16("RootList"); UniquePtr<EdgeRange> -Concrete<RootList>::edges(JSContext* cx, bool wantNames) const { +Concrete<RootList>::edges(JSRuntime* rt, bool wantNames) const { MOZ_ASSERT_IF(wantNames, get().wantNames); - return UniquePtr<EdgeRange>(cx->new_<PreComputedEdgeRange>(cx, get().edges)); + return UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges)); } } // namespace ubi } // namespace JS
--- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -24,21 +24,21 @@ namespace js { * versions. If deserialization fails, the data should be invalidated if * possible. * * When you change this, run make_opcode_doc.py and copy the new output into * this wiki page: * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 307; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 309; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); -static_assert(JSErr_Limit == 408, +static_assert(JSErr_Limit == 413, "GREETINGS, POTENTIAL SUBTRAHEND INCREMENTER! If you added or " "removed MSG_DEFs from js.msg, you should increment " "XDR_BYTECODE_VERSION_SUBTRAHEND and update this assertion's " "expected JSErr_Limit value."); class XDRBuffer { public: explicit XDRBuffer(JSContext* cx)
--- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -6639,46 +6639,46 @@ nsLayoutUtils::GetTextRunFlagsForStyle(n break; } return result | GetTextRunOrientFlagsForStyle(aStyleContext); } /* static */ uint32_t nsLayoutUtils::GetTextRunOrientFlagsForStyle(nsStyleContext* aStyleContext) { - WritingMode wm(aStyleContext); - if (wm.IsVertical()) { + uint8_t writingMode = aStyleContext->StyleVisibility()->mWritingMode; + switch (writingMode) { + case NS_STYLE_WRITING_MODE_HORIZONTAL_TB: + return gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL; + + case NS_STYLE_WRITING_MODE_VERTICAL_LR: + case NS_STYLE_WRITING_MODE_VERTICAL_RL: switch (aStyleContext->StyleVisibility()->mTextOrientation) { case NS_STYLE_TEXT_ORIENTATION_MIXED: return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED; case NS_STYLE_TEXT_ORIENTATION_UPRIGHT: return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT; case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS: - // This should depend on writing mode vertical-lr vs vertical-rl, - // but until we support SIDEWAYS_LEFT, we'll treat this the same - // as SIDEWAYS_RIGHT and simply fall through. - /* - if (wm.IsVerticalLR()) { - return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; - } else { - return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; - } - */ - case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT: return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; - case NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT: - // Not yet supported, so fall through to the default (error) case. - /* - return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; - */ default: NS_NOTREACHED("unknown text-orientation"); - } - } - return 0; + return 0; + } + + /* not yet implemented: + case NS_STYLE_WRITING_MODE_SIDEWAYS_LR: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT; + */ + case NS_STYLE_WRITING_MODE_SIDEWAYS_RL: + return gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; + + default: + NS_NOTREACHED("unknown writing-mode"); + return 0; + } } /* static */ void nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2, nsRect* aHStrip, nsRect* aVStrip) { NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(), "expected rects at the same position"); nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width),
--- a/layout/generic/WritingModes.h +++ b/layout/generic/WritingModes.h @@ -293,17 +293,23 @@ public: // bit 1 = the eBlockFlowMask value static const mozilla::css::Side kLogicalBlockSides[][2] = { { NS_SIDE_TOP, NS_SIDE_BOTTOM }, // horizontal-tb { NS_SIDE_RIGHT, NS_SIDE_LEFT }, // vertical-rl { NS_SIDE_BOTTOM, NS_SIDE_TOP }, // (horizontal-bt) { NS_SIDE_LEFT, NS_SIDE_RIGHT }, // vertical-lr }; + // Ignore the SIDEWAYS_MASK bit of the writing-mode value, as this has no + // effect on the side mappings. + aWritingModeValue &= ~NS_STYLE_WRITING_MODE_SIDEWAYS_MASK; + + // What's left of the writing-mode should be in the range 0-3: NS_ASSERTION(aWritingModeValue < 4, "invalid aWritingModeValue value"); + return kLogicalBlockSides[aWritingModeValue][aEdge]; } mozilla::Side PhysicalSideForInlineAxis(LogicalEdge aEdge) const { // indexes are four-bit values: // bit 0 = the eOrientationMask value // bit 1 = the eInlineFlowMask value @@ -449,51 +455,52 @@ public: break; case NS_STYLE_WRITING_MODE_VERTICAL_LR: { mWritingMode = eBlockFlowMask | eLineOrientMask | eOrientationMask; uint8_t textOrientation = aStyleContext->StyleVisibility()->mTextOrientation; -#if 0 // not yet implemented - if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) { - mWritingMode &= ~eLineOrientMask; - } -#endif - if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) { + if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS) { mWritingMode |= eSidewaysMask; } break; } case NS_STYLE_WRITING_MODE_VERTICAL_RL: { mWritingMode = eOrientationMask; uint8_t textOrientation = aStyleContext->StyleVisibility()->mTextOrientation; -#if 0 // not yet implemented - if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) { - mWritingMode |= eLineOrientMask; - } -#endif - if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) { + if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS) { mWritingMode |= eSidewaysMask; } break; } + case NS_STYLE_WRITING_MODE_SIDEWAYS_LR: + mWritingMode = eBlockFlowMask | + eInlineFlowMask | + eOrientationMask | + eSidewaysMask; + break; + + case NS_STYLE_WRITING_MODE_SIDEWAYS_RL: + mWritingMode = eOrientationMask | + eSidewaysMask; + break; + default: NS_NOTREACHED("unknown writing mode!"); mWritingMode = 0; break; } if (NS_STYLE_DIRECTION_RTL == styleVisibility->mDirection) { - mWritingMode |= eInlineFlowMask | //XXX needs update when text-orientation added - eBidiMask; + mWritingMode ^= eInlineFlowMask | eBidiMask; } } /** * This function performs fixup for elements with 'unicode-bidi: plaintext', * where inline directionality is derived from the Unicode bidi categories * of the element's content, and not the CSS 'direction' property. * @@ -535,16 +542,31 @@ public: */ bool IsOrthogonalTo(const WritingMode& aOther) const { return IsVertical() != aOther.IsVertical(); } uint8_t GetBits() const { return mWritingMode; } +#ifdef DEBUG + const char* DebugString() const { + return IsVertical() + ? IsVerticalLR() + ? IsBidiLTR() + ? IsSideways() ? "sw-lr-ltr" : "v-lr-ltr" + : IsSideways() ? "sw-lr-rtl" : "v-lr-rtl" + : IsBidiLTR() + ? IsSideways() ? "sw-rl-ltr" : "v-rl-ltr" + : IsSideways() ? "sw-rl-rtl" : "v-rl-rtl" + : IsBidiLTR() ? "h-ltr" : "h-rtl" + ; + } +#endif + private: friend class LogicalPoint; friend class LogicalSize; friend class LogicalMargin; friend class LogicalRect; friend struct IPC::ParamTraits<WritingMode>; // IMENotification cannot store this class directly since this has some @@ -575,17 +597,20 @@ private: eOrientationMask = 0x01, // true means vertical text eInlineFlowMask = 0x02, // true means absolute RTL/BTT (against physical coords) eBlockFlowMask = 0x04, // true means vertical-LR (or horizontal-BT if added) eLineOrientMask = 0x08, // true means over != block-start eBidiMask = 0x10, // true means line-relative RTL (bidi RTL) // Note: We have one excess bit of info; WritingMode can pack into 4 bits. // But since we have space, we're caching interesting things for fast access. - eSidewaysMask = 0x20, // true means text-orientation is sideways-*, + eSidewaysMask = 0x20, // true means text is being rendered vertically + // using rotated glyphs (i.e. writing-mode is + // sideways-*, or writing-mode is vertical-* AND + // text-orientation is sideways), // which means we'll use alphabetic instead of // centered default baseline for vertical text // Masks for output enums eInlineMask = 0x03, eBlockMask = 0x05 }; };
--- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -5753,35 +5753,29 @@ nsIFrame::ListGeneric(nsACString& aTo, c void* IBprevsibling = Properties().Get(IBSplitPrevSibling()); if (IBprevsibling) { aTo += nsPrintfCString(" IBSplitPrevSibling=%p", IBprevsibling); } aTo += nsPrintfCString(" {%d,%d,%d,%d}", mRect.x, mRect.y, mRect.width, mRect.height); mozilla::WritingMode wm = GetWritingMode(); if (wm.IsVertical() || !wm.IsBidiLTR()) { - aTo += nsPrintfCString(" wm=%s-%s: logical size={%d,%d}", - wm.IsVertical() ? wm.IsVerticalLR() ? "vlr" : "vrl" - : "htb", - wm.IsBidiLTR() ? "ltr" : "rtl", + aTo += nsPrintfCString(" wm=%s: logical size={%d,%d}", wm.DebugString(), ISize(), BSize()); } nsIFrame* parent = GetParent(); if (parent) { WritingMode pWM = parent->GetWritingMode(); if (pWM.IsVertical() || !pWM.IsBidiLTR()) { nsSize containerSize = parent->mRect.Size(); LogicalRect lr(pWM, mRect, containerSize); - aTo += nsPrintfCString(" parent wm=%s-%s, cs={%d,%d}, " + aTo += nsPrintfCString(" parent wm=%s, cs={%d,%d}, " " logicalRect={%d,%d,%d,%d}", - pWM.IsVertical() ? pWM.IsVerticalLR() - ? "vlr" : "vrl" - : "htb", - wm.IsBidiLTR() ? "ltr" : "rtl", + pWM.DebugString(), containerSize.width, containerSize.height, lr.IStart(pWM), lr.BStart(pWM), lr.ISize(pWM), lr.BSize(pWM)); } } nsIFrame* f = const_cast<nsIFrame*>(this); if (f->HasOverflowAreas()) { nsRect vo = f->GetVisualOverflowRect();
--- a/layout/generic/nsLineBox.cpp +++ b/layout/generic/nsLineBox.cpp @@ -245,21 +245,18 @@ nsLineBox::List(FILE* out, const char* a StateToString(cbuf, sizeof(cbuf))); if (IsBlock() && !GetCarriedOutBEndMargin().IsZero()) { str += nsPrintfCString("bm=%d ", GetCarriedOutBEndMargin().get()); } nsRect bounds = GetPhysicalBounds(); str += nsPrintfCString("{%d,%d,%d,%d} ", bounds.x, bounds.y, bounds.width, bounds.height); if (mWritingMode.IsVertical() || !mWritingMode.IsBidiLTR()) { - str += nsPrintfCString("{%s-%s: %d,%d,%d,%d; cs=%d,%d} ", - mWritingMode.IsVertical() - ? mWritingMode.IsVerticalLR() ? "vlr" : "vrl" - : "htb", - mWritingMode.IsBidiLTR() ? "ltr" : "rtl", + str += nsPrintfCString("{%s: %d,%d,%d,%d; cs=%d,%d} ", + mWritingMode.DebugString(), IStart(), BStart(), ISize(), BSize(), mContainerSize.width, mContainerSize.height); } if (mData && (!mData->mOverflowAreas.VisualOverflow().IsEqualEdges(bounds) || !mData->mOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds))) { str += nsPrintfCString("vis-overflow=%d,%d,%d,%d scr-overflow=%d,%d,%d,%d ", mData->mOverflowAreas.VisualOverflow().x,
--- a/layout/reftests/svg/as-image/reftest.list +++ b/layout/reftests/svg/as-image/reftest.list @@ -41,17 +41,17 @@ skip-if(B2G||Mulet) == canvas-drawImage- skip-if(B2G||Mulet) == canvas-drawImage-scale-1b.html lime100x100-ref.html # Initial mulet triage: parity with B2G/B2G Desktop skip-if(B2G||Mulet) == canvas-drawImage-scale-1c.html lime100x100-ref.html # Initial mulet triage: parity with B2G/B2G Desktop # Fails on Android versions where we apply a zoom by default, because the # resolution of a canvas element is fixed regardless of zoom level. fuzzy(1,2) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-scale-2a.html canvas-drawImage-scale-2-ref.html fuzzy(1,2) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-scale-2b.html canvas-drawImage-scale-2-ref.html -fuzzy-if(winWidget&&!d2d,1,10000) fuzzy-if(Android||B2G,1,10000) == canvas-drawImage-alpha-1.html canvas-drawImage-alpha-1-ref.html +fuzzy-if(winWidget&&!d2d,1,10000) fuzzy-if(azureSkia,1,10000) fuzzy-if(Android||B2G,1,10000) == canvas-drawImage-alpha-1.html canvas-drawImage-alpha-1-ref.html #Same as scale-2a but with globalAlpha: fuzzy(1,2) fuzzy-if(azureSkia,1,40000) fails-if(Android&&AndroidVersion<15&&AndroidVersion!=10) == canvas-drawImage-alpha-2.html canvas-drawImage-alpha-2-ref.html skip-if(B2G||Mulet) == canvas-drawImage-slice-1a.html lime100x100-ref.html # Initial mulet triage: parity with B2G/B2G Desktop == canvas-drawImage-slice-1b.html lime100x100-ref.html == canvas-drawImage-origin-clean-1.html lime100x100-ref.html == canvas-drawImage-transform-restored.html canvas-drawImage-transform-restored-ref.html
--- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -479,19 +479,20 @@ CSS_KEY(select-before, select_before) CSS_KEY(select-menu, select_menu) CSS_KEY(select-same, select_same) CSS_KEY(semi-condensed, semi_condensed) CSS_KEY(semi-expanded, semi_expanded) CSS_KEY(separate, separate) CSS_KEY(sepia, sepia) CSS_KEY(serif, serif) CSS_KEY(show, show) -/* CSS_KEY(sideways, sideways) */ -/* CSS_KEY(sideways-left, sideways_left) */ -CSS_KEY(sideways-right, sideways_right) +CSS_KEY(sideways, sideways) +/*CSS_KEY(sideways-lr, sideways_lr)*/ +CSS_KEY(sideways-right, sideways_right) /* alias for 'sideways' */ +CSS_KEY(sideways-rl, sideways_rl) CSS_KEY(simp-chinese-formal, simp_chinese_formal) CSS_KEY(simp-chinese-informal, simp_chinese_informal) CSS_KEY(simplified, simplified) CSS_KEY(skew, skew) CSS_KEY(skewx, skewx) CSS_KEY(skewy, skewy) CSS_KEY(slashed-zero, slashed_zero) CSS_KEY(slice, slice)
--- a/layout/style/nsCSSProps.cpp +++ b/layout/style/nsCSSProps.cpp @@ -1770,19 +1770,18 @@ const KTableValue nsCSSProps::kTextDecor eCSSKeyword_dashed, NS_STYLE_TEXT_DECORATION_STYLE_DASHED, eCSSKeyword_wavy, NS_STYLE_TEXT_DECORATION_STYLE_WAVY, eCSSKeyword_UNKNOWN,-1 }; const KTableValue nsCSSProps::kTextOrientationKTable[] = { eCSSKeyword_mixed, NS_STYLE_TEXT_ORIENTATION_MIXED, eCSSKeyword_upright, NS_STYLE_TEXT_ORIENTATION_UPRIGHT, - eCSSKeyword_sideways_right, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT, - /* eCSSKeyword_sideways_left, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT, */ - /* eCSSKeyword_sideways, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS, */ + eCSSKeyword_sideways, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS, + eCSSKeyword_sideways_right, NS_STYLE_TEXT_ORIENTATION_SIDEWAYS, eCSSKeyword_UNKNOWN, -1 }; const KTableValue nsCSSProps::kTextOverflowKTable[] = { eCSSKeyword_clip, NS_STYLE_TEXT_OVERFLOW_CLIP, eCSSKeyword_ellipsis, NS_STYLE_TEXT_OVERFLOW_ELLIPSIS, eCSSKeyword_UNKNOWN, -1 }; @@ -1940,16 +1939,18 @@ const KTableValue nsCSSProps::kWordWrapK eCSSKeyword_break_word, NS_STYLE_WORDWRAP_BREAK_WORD, eCSSKeyword_UNKNOWN,-1 }; const KTableValue nsCSSProps::kWritingModeKTable[] = { eCSSKeyword_horizontal_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB, eCSSKeyword_vertical_lr, NS_STYLE_WRITING_MODE_VERTICAL_LR, eCSSKeyword_vertical_rl, NS_STYLE_WRITING_MODE_VERTICAL_RL, +/* eCSSKeyword_sideways_lr, NS_STYLE_WRITING_MODE_SIDEWAYS_LR, */ + eCSSKeyword_sideways_rl, NS_STYLE_WRITING_MODE_SIDEWAYS_RL, eCSSKeyword_lr, NS_STYLE_WRITING_MODE_HORIZONTAL_TB, eCSSKeyword_lr_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB, eCSSKeyword_rl, NS_STYLE_WRITING_MODE_HORIZONTAL_TB, eCSSKeyword_rl_tb, NS_STYLE_WRITING_MODE_HORIZONTAL_TB, eCSSKeyword_tb, NS_STYLE_WRITING_MODE_VERTICAL_RL, eCSSKeyword_tb_rl, NS_STYLE_WRITING_MODE_VERTICAL_RL, eCSSKeyword_UNKNOWN, -1 };
--- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -7636,16 +7636,18 @@ nsRuleNode::ComputePositionData(void* aS default: MOZ_ASSERT(false, "unexpected writing-mode value"); // fall through case NS_STYLE_WRITING_MODE_HORIZONTAL_TB: vertical = false; break; case NS_STYLE_WRITING_MODE_VERTICAL_RL: case NS_STYLE_WRITING_MODE_VERTICAL_LR: + case NS_STYLE_WRITING_MODE_SIDEWAYS_RL: + case NS_STYLE_WRITING_MODE_SIDEWAYS_LR: vertical = true; break; } const nsCSSValue* width = aRuleData->ValueForWidth(); SetCoord(width->GetUnit() == eCSSUnit_Enumerated && vertical ? nsCSSValue(eCSSUnit_Unset) : *width, pos->mWidth, parentPos->mWidth,
--- a/layout/style/nsStyleConsts.h +++ b/layout/style/nsStyleConsts.h @@ -394,22 +394,35 @@ static inline mozilla::css::Side operato #define NS_STYLE_CURSOR_EW_RESIZE 35 #define NS_STYLE_CURSOR_NONE 36 // See nsStyleVisibility #define NS_STYLE_DIRECTION_LTR 0 #define NS_STYLE_DIRECTION_RTL 1 // See nsStyleVisibility -// WritingModes.h depends on the particular values used here +// NOTE: WritingModes.h depends on the particular values used here. #define NS_STYLE_WRITING_MODE_HORIZONTAL_TB 0 #define NS_STYLE_WRITING_MODE_VERTICAL_RL 1 // #define NS_STYLE_WRITING_MODE_HORIZONTAL_BT 2 // hypothetical #define NS_STYLE_WRITING_MODE_VERTICAL_LR 3 +// Single-bit flag, used in combination with VERTICAL_LR and _RL to specify +// the corresponding SIDEWAYS_* modes. +// (To avoid ambiguity, this bit must be high enough such that no other +// values here accidentally use it in their binary representation.) +#define NS_STYLE_WRITING_MODE_SIDEWAYS_MASK 4 + +#define NS_STYLE_WRITING_MODE_SIDEWAYS_RL \ + (NS_STYLE_WRITING_MODE_VERTICAL_RL | \ + NS_STYLE_WRITING_MODE_SIDEWAYS_MASK) +#define NS_STYLE_WRITING_MODE_SIDEWAYS_LR \ + (NS_STYLE_WRITING_MODE_VERTICAL_LR | \ + NS_STYLE_WRITING_MODE_SIDEWAYS_MASK) + // See nsStyleDisplay #define NS_STYLE_DISPLAY_NONE 0 #define NS_STYLE_DISPLAY_BLOCK 1 #define NS_STYLE_DISPLAY_INLINE 2 #define NS_STYLE_DISPLAY_INLINE_BLOCK 3 #define NS_STYLE_DISPLAY_LIST_ITEM 4 #define NS_STYLE_DISPLAY_TABLE 8 #define NS_STYLE_DISPLAY_INLINE_TABLE 9 @@ -874,19 +887,17 @@ static inline mozilla::css::Side operato // See nsStyleText #define NS_STYLE_TEXT_SIZE_ADJUST_NONE 0 #define NS_STYLE_TEXT_SIZE_ADJUST_AUTO 1 // See nsStyleText #define NS_STYLE_TEXT_ORIENTATION_MIXED 0 #define NS_STYLE_TEXT_ORIENTATION_UPRIGHT 1 -#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT 2 -#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT 3 /* placeholder, not yet parsed */ -#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS 4 /* placeholder, not yet parsed */ +#define NS_STYLE_TEXT_ORIENTATION_SIDEWAYS 2 // See nsStyleText #define NS_STYLE_TEXT_COMBINE_UPRIGHT_NONE 0 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_ALL 1 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_2 2 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_3 3 #define NS_STYLE_TEXT_COMBINE_UPRIGHT_DIGITS_4 4
--- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -4572,22 +4572,34 @@ var gCSSProperties = { function logical_axis_prop_get_computed(cs, property) { // Use defaults for these two properties in case the vertical text // pref (which they live behind) is turned off. var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb"; var orientation = writingMode.substring(0, writingMode.indexOf("-")); var mappings = { - "block-size": { horizontal: "height", vertical: "width" }, - "inline-size": { horizontal: "width", vertical: "height" }, - "max-block-size": { horizontal: "max-height", vertical: "max-width" }, - "max-inline-size": { horizontal: "max-width", vertical: "max-height" }, - "min-block-size": { horizontal: "min-height", vertical: "min-width" }, - "min-inline-size": { horizontal: "min-width", vertical: "min-height" }, + "block-size": { horizontal: "height", + vertical: "width", + sideways: "width" }, + "inline-size": { horizontal: "width", + vertical: "height", + sideways: "height" }, + "max-block-size": { horizontal: "max-height", + vertical: "max-width", + sideways: "max-width" }, + "max-inline-size": { horizontal: "max-width", + vertical: "max-height", + sideways: "max-height" }, + "min-block-size": { horizontal: "min-height", + vertical: "min-width", + sideways: "min-width" }, + "min-inline-size": { horizontal: "min-width", + vertical: "min-height", + sideways: "min-height" }, }; if (!mappings[property]) { throw "unexpected property " + property; } var prop = mappings[property][orientation]; if (!prop) { @@ -4596,61 +4608,52 @@ function logical_axis_prop_get_computed( return cs.getPropertyValue(prop); } function logical_box_prop_get_computed(cs, property) { // http://dev.w3.org/csswg/css-writing-modes-3/#logical-to-physical - // Use defaults for these two properties in case the vertical text - // pref (which they live behind) is turned off. + // Use default for writing-mode in case the vertical text + // pref (which it lives behind) is turned off. var writingMode = cs.getPropertyValue("writing-mode") || "horizontal-tb"; - var textOrientation = cs.getPropertyValue("text-orientation") || "mixed"; var direction = cs.getPropertyValue("direction"); - // We only need to distinguish between text-orientation values of - // sideways-left and {mixed,upright,sideways-right} (which we will - // call "others"). - - if (textOrientation == "sideways") { - // text-orientation does not contribute to the logical to physical - // mapping when writing-mode is horizontal-tb, so it doesn't matter - // that we convert it to sideways-left in that case. - textOrientation = writingMode == "vertical-rl" ? "others" : "sideways-left"; - } else if (textOrientation != "sideways-left") { - textOrientation = "others"; - } - // keys in blockMappings are writing-mode values var blockMappings = { "horizontal-tb": { "start": "top", "end": "bottom" }, "vertical-rl": { "start": "right", "end": "left" }, "vertical-lr": { "start": "left", "end": "right" }, + "sideways-rl": { "start": "right", "end": "left" }, + "sideways-lr": { "start": "left", "end": "right" }, }; // keys in inlineMappings are regular expressions that match against - // a {writing-mode,text-orientation,direction} triple as a space- - // separated string + // a {writing-mode,direction} pair as a space-separated string var inlineMappings = { - "horizontal-tb \\S+ ltr": { "start": "left", "end": "right" }, - "horizontal-tb \\S+ rtl": { "start": "right", "end": "left" }, - "vertical-.. sideways-left ltr": { "start": "bottom", "end": "top" }, - "vertical-.. sideways-left rtl": { "start": "top", "end": "bottom" }, - "vertical-.. others ltr": { "start": "top", "end": "bottom" }, - "vertical-.. others rtl": { "start": "bottom", "end": "top" }, + "horizontal-tb ltr": { "start": "left", "end": "right" }, + "horizontal-tb rtl": { "start": "right", "end": "left" }, + "vertical-.. ltr": { "start": "bottom", "end": "top" }, + "vertical-.. rtl": { "start": "top", "end": "bottom" }, + "vertical-.. ltr": { "start": "top", "end": "bottom" }, + "vertical-.. rtl": { "start": "bottom", "end": "top" }, + "sideways-lr ltr": { "start": "bottom", "end": "top" }, + "sideways-lr rtl": { "start": "top", "end": "bottom" }, + "sideways-rl ltr": { "start": "top", "end": "bottom" }, + "sideways-rl rtl": { "start": "bottom", "end": "top" }, }; var blockMapping = blockMappings[writingMode]; var inlineMapping; // test each regular expression in inlineMappings against the - // {writing-mode,text-orientation,direction} triple - var key = `${writingMode} ${textOrientation} ${direction}`; + // {writing-mode,direction} pair + var key = `${writingMode} ${direction}`; for (var k in inlineMappings) { if (new RegExp(k).test(key)) { inlineMapping = inlineMappings[k]; break; } } if (!blockMapping || !inlineMapping) { @@ -4723,26 +4726,26 @@ if (SpecialPowers.getBoolPref("layout.cs if (SpecialPowers.getBoolPref("layout.css.vertical-text.enabled")) { var verticalTextProperties = { "writing-mode": { domProp: "writingMode", inherited: true, type: CSS_TYPE_LONGHAND, initial_values: [ "horizontal-tb", "lr", "lr-tb", "rl", "rl-tb" ], - other_values: [ "vertical-lr", "vertical-rl", "tb", "tb-rl" ], - invalid_values: [ "10px", "30%", "justify", "auto", "1em" ] + other_values: [ "vertical-lr", "vertical-rl", "sideways-rl", "tb", "tb-rl" ], + invalid_values: [ "10px", "30%", "justify", "auto", "1em", "sideways-lr" ] /* sideways-lr not yet supported */ }, "text-orientation": { domProp: "textOrientation", inherited: true, type: CSS_TYPE_LONGHAND, initial_values: [ "mixed" ], - other_values: [ "upright", "sideways-right" ], - invalid_values: [ "none", "3em", "sideways", "sideways-left" ] /* sideways, sideways-left not yet supported */ + other_values: [ "upright", "sideways", "sideways-right" ], /* sideways-right alias for backward compatibility */ + invalid_values: [ "none", "3em", "sideways-left" ] /* sideways-left removed from CSS Writing Modes */ }, "border-block-end": { domProp: "borderBlockEnd", inherited: false, type: CSS_TYPE_TRUE_SHORTHAND, subproperties: [ "border-block-end-color", "border-block-end-style", "border-block-end-width" ], initial_values: [ "none", "medium", "currentColor", "thin", "none medium currentcolor" ], other_values: [ "solid", "green", "medium solid", "green solid", "10px solid", "thick solid", "5px green none" ],
--- a/layout/style/test/test_logical_properties.html +++ b/layout/style/test/test_logical_properties.html @@ -38,70 +38,52 @@ var gAxisPropertyGroups; // values to use while testing var gValues = { "length": ["1px", "2px", "3px", "4px", "5px"], "color": ["rgb(1, 1, 1)", "rgb(2, 2, 2)", "rgb(3, 3, 3)", "rgb(4, 4, 4)", "rgb(5, 5, 5)"], "border-style": ["solid", "dashed", "dotted", "double", "groove"], }; -// six unique overall writing modes +// Six unique overall writing modes for property-mapping purposes. +// Note that text-orientation does not affect these mappings, now that +// the proposed sideways-left value no longer exists (superseded in CSS +// Writing Modes by writing-mode: sideways-lr). var gWritingModes = [ { style: [ - "writing-mode: horizontal-tb; text-orientation: mixed; direction: ltr; ", - "writing-mode: horizontal-tb; text-orientation: upright; direction: ltr; ", - "writing-mode: horizontal-tb; text-orientation: sideways-right; direction: ltr; ", - // XXX See the todo()s below. - // "writing-mode: horizontal-tb; text-orientation: sideways-left; direction: ltr; ", - // "writing-mode: horizontal-tb; text-orientation: sideways; direction: ltr; ", + "writing-mode: horizontal-tb; direction: ltr; ", ], blockStart: "top", blockEnd: "bottom", inlineStart: "left", inlineEnd: "right", block: "vertical", inline: "horizontal" }, { style: [ - "writing-mode: horizontal-tb; text-orientation: mixed; direction: rtl; ", - "writing-mode: horizontal-tb; text-orientation: upright; direction: rtl; ", - "writing-mode: horizontal-tb; text-orientation: sideways-right; direction: rtl; ", - // "writing-mode: horizontal-tb; text-orientation: sideways-left; direction: rtl; ", - // "writing-mode: horizontal-tb; text-orientation: sideways; direction: rtl; ", + "writing-mode: horizontal-tb; direction: rtl; ", ], blockStart: "top", blockEnd: "bottom", inlineStart: "right", inlineEnd: "left", block: "vertical", inline: "horizontal" }, { style: [ - "writing-mode: vertical-rl; text-orientation: mixed; direction: rtl; ", - "writing-mode: vertical-rl; text-orientation: upright; direction: rtl; ", - "writing-mode: vertical-rl; text-orientation: sideways-right; direction: rtl; ", - // "writing-mode: vertical-rl; text-orientation: sideways-left; direction: ltr; ", - // "writing-mode: vertical-rl; text-orientation: sideways; direction: rtl; ", + "writing-mode: vertical-rl; direction: rtl; ", + "writing-mode: sideways-rl; direction: rtl; ", ], blockStart: "right", blockEnd: "left", inlineStart: "bottom", inlineEnd: "top", block: "horizontal", inline: "vertical" }, { style: [ - "writing-mode: vertical-rl; text-orientation: mixed; direction: ltr; ", - "writing-mode: vertical-rl; text-orientation: upright; direction: ltr; ", - "writing-mode: vertical-rl; text-orientation: sideways-right; direction: ltr; ", - // "writing-mode: vertical-rl; text-orientation: sideways-left; direction: rtl; ", - // "writing-mode: vertical-rl; text-orientation: sideways; direction: ltr; ", + "writing-mode: vertical-rl; direction: ltr; ", + "writing-mode: sideways-rl; direction: ltr; ", ], blockStart: "right", blockEnd: "left", inlineStart: "top", inlineEnd: "bottom", block: "horizontal", inline: "vertical" }, { style: [ - "writing-mode: vertical-lr; text-orientation: mixed; direction: rtl; ", - "writing-mode: vertical-lr; text-orientation: upright; direction: rtl; ", - "writing-mode: vertical-lr; text-orientation: sideways-right; direction: rtl; ", - // "writing-mode: vertical-lr; text-orientation: sideways-left; direction: ltr; ", - // "writing-mode: vertical-lr; text-orientation: sideways; direction: ltr; ", + "writing-mode: vertical-lr; direction: rtl; ", + // "writing-mode: sideways-lr; direction: ltr; ", ], blockStart: "left", blockEnd: "right", inlineStart: "bottom", inlineEnd: "top", block: "horizontal", inline: "vertical" }, { style: [ - "writing-mode: vertical-lr; text-orientation: mixed; direction: ltr; ", - "writing-mode: vertical-lr; text-orientation: upright; direction: ltr; ", - "writing-mode: vertical-lr; text-orientation: sideways-right; direction: ltr; ", - // "writing-mode: vertical-lr; text-orientation: sideways-left; direction: rtl; ", - // "writing-mode: vertical-lr; text-orientation: sideways; direction: rtl; ", + "writing-mode: vertical-lr; direction: ltr; ", + // "writing-mode: sideways-lr; direction: rtl; ", ], blockStart: "left", blockEnd: "right", inlineStart: "top", inlineEnd: "bottom", block: "horizontal", inline: "vertical" }, ]; function init() { gBoxPropertyGroups = []; @@ -157,33 +139,26 @@ function init() { horizontal: `${aPrefix}width`, vertical: `${aPrefix}height`, inline: `${aPrefix}inline-size`, block: `${aPrefix}block-size`, type: "length", prerequisites: make_declaration(gCSSProperties[`${aPrefix}height`].prerequisites) }); }); - // Assume that sideways-left and sideways keywords are still not parsed yet - // for text-orientation. When we start supporting these keywords, the - // entries in the .style properties of the gWritingModes objects above - // should be uncommented. + // Assume that the sideways-lr keyword is not parsed yet for writing-mode. + // When we start supporting this keyword, the entries in the .style + // properties of the gWritingModes objects above should be uncommented. var s = document.createElement("style"); document.body.appendChild(s); s.textContent = "div { }"; - s.sheet.cssRules[0].style.textOrientation = "sideways-left"; - todo(s.sheet.cssRules[0].style.textOrientation, "sideways-left", - "uncomment sideways-left cases from gWritingModes and " + - "remove this todo()!"); - - s.textContent = "div { }"; - s.sheet.cssRules[0].style.textOrientation = "sideways"; - todo(s.sheet.cssRules[0].style.textOrientation, "sideways", - "uncomment sideways cases from gWritingModes and " + + s.sheet.cssRules[0].style.writingMode = "sideways-lr"; + todo(s.sheet.cssRules[0].style.writingMode, "sideways-lr", + "uncomment sideways-lr cases from gWritingModes and " + "remove this todo()!"); s.remove(); } function test_computed_values(aTestName, aRules, aExpectedValues) { gSheet.textContent = aRules; var cs = getComputedStyle(gTest);
--- a/media/webrtc/trunk/webrtc/common_types.h +++ b/media/webrtc/trunk/webrtc/common_types.h @@ -782,19 +782,20 @@ struct OverUseDetectorOptions { double initial_e[2][2]; double initial_process_noise[2]; double initial_avg_noise; double initial_var_noise; double initial_threshold; }; enum CPULoadState { - kLoadRelaxed, + kLoadRelaxed = 0, kLoadNormal, - kLoadStressed + kLoadStressed, + kLoadLast, }; class CPULoadStateObserver { public: virtual void onLoadStateChanged(CPULoadState aNewState) = 0; virtual ~CPULoadStateObserver() {}; };
--- a/mfbt/WeakPtr.h +++ b/mfbt/WeakPtr.h @@ -167,17 +167,23 @@ public: WeakPtr(const WeakPtr& aOther) { *this = aOther; } WeakPtr& operator=(T* aOther) { - return *this = aOther->SelfReferencingWeakPtr(); + if (aOther) { + *this = aOther->SelfReferencingWeakPtr(); + } else if (!mRef || mRef->get()) { + // Ensure that mRef is dereferenceable in the uninitialized state. + mRef = new WeakReference(nullptr); + } + return *this; } MOZ_IMPLICIT WeakPtr(T* aOther) { *this = aOther; } // Ensure that mRef is dereferenceable in the uninitialized state.
--- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -1707,17 +1707,17 @@ var BrowserApp = { Log.e("Browser", "Incorrect index " + index + " truncated to " + historySize - 1); index = historySize - 1; } browser.gotoIndex(index); break; case "Session:Reload": { - let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE; + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY; // Check to see if this is a message to enable/disable mixed content blocking. if (aData) { let data = JSON.parse(aData); if (data.contentType === "tracking") { // Convert document URI into the format used by // nsChannelClassifier::ShouldEnableTrackingProtection // (any scheme turned into https is correct) @@ -3597,18 +3597,17 @@ Tab.prototype = { } // Only reload the page for http/https schemes let currentURI = this.browser.currentURI; if (!currentURI.schemeIs("http") && !currentURI.schemeIs("https")) return; let url = currentURI.spec; - let flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE | - Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; + let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY; if (this.originalURI && !this.originalURI.equals(currentURI)) { // We were redirected; reload the original URL url = this.originalURI.spec; } this.browser.docShell.loadURI(url, flags, null, null, null); },
--- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -141,19 +141,16 @@ pref("dom.select_events.enabled", true); pref("dom.select_events.enabled", false); #endif // Whether or not Web Workers are enabled. pref("dom.workers.enabled", true); // The number of workers per domain allowed to run concurrently. pref("dom.workers.maxPerDomain", 20); -// Whether or not Shared Web Workers are enabled. -pref("dom.workers.sharedWorkers.enabled", true); - pref("dom.serviceWorkers.enabled", false); // Allow service workers to intercept network requests using the fetch event pref("dom.serviceWorkers.interception.enabled", false); // Allow service workers to intercept opaque (cross origin) responses pref("dom.serviceWorkers.interception.opaque.enabled", false); @@ -680,17 +677,17 @@ pref("gfx.font_rendering.opentype_svg.en // e.g., pref("gfx.canvas.azure.backends", "direct2d,skia,cairo"); pref("gfx.canvas.azure.backends", "direct2d1.1,direct2d,skia,cairo"); pref("gfx.content.azure.backends", "direct2d1.1,direct2d,cairo"); #else #ifdef XP_MACOSX pref("gfx.content.azure.backends", "cg"); pref("gfx.canvas.azure.backends", "skia"); // Accelerated cg canvas where available (10.7+) -pref("gfx.canvas.azure.accelerated", false); +pref("gfx.canvas.azure.accelerated", true); #else pref("gfx.canvas.azure.backends", "cairo"); pref("gfx.content.azure.backends", "cairo"); #endif #endif #ifdef MOZ_WIDGET_GTK2 pref("gfx.content.azure.backends", "cairo");
--- a/netwerk/cookie/nsCookieService.cpp +++ b/netwerk/cookie/nsCookieService.cpp @@ -2878,16 +2878,33 @@ nsCookieService::SetCookieInternal(nsIUR COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests"); return newCookie; } if (!CheckPath(cookieAttributes, aHostURI)) { COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests"); return newCookie; } + // reject cookie if value contains an RFC 6265 disallowed character - see + // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423 + // NOTE: this is not the full set of characters disallowed by 6265 - notably + // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is + // for parity with Chrome. This only applies to cookies set via the Set-Cookie + // header, as document.cookie is defined to be UTF-8. Hooray for + // symmetry!</sarcasm> + const char illegalCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, + 0x1E, 0x1F, 0x3B, 0x00 }; + if (aFromHttp && (cookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) { + COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character"); + return newCookie; + } + // create a new nsCookie and copy attributes nsRefPtr<nsCookie> cookie = nsCookie::Create(cookieAttributes.name, cookieAttributes.value, cookieAttributes.host, cookieAttributes.path, cookieAttributes.expiryTime, currentTimeInUsec,
new file mode 100644 --- /dev/null +++ b/netwerk/test/unit/test_cookie_blacklist.js @@ -0,0 +1,16 @@ +const GOOD_COOKIE = "GoodCookie=OMNOMNOM"; + +function run_test() { + var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); + var cookieURI = ios.newURI("http://mozilla.org/test_cookie_blacklist.js", + null, null); + + var cookieService = Cc["@mozilla.org/cookieService;1"] + .getService(Ci.nsICookieService); + cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie1=\x01", null, null); + cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, "BadCookie2=\v", null, null); + cookieService.setCookieStringFromHttp(cookieURI, cookieURI, null, GOOD_COOKIE, null, null); + + var storedCookie = cookieService.getCookieString(cookieURI, null); + do_check_eq(storedCookie, GOOD_COOKIE); +}
--- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -324,8 +324,9 @@ skip-if = os == "android" [test_packaged_app_service.js] [test_packaged_app_verifier.js] [test_suspend_channel_before_connect.js] [test_inhibit_caching.js] [test_dns_disable_ipv4.js] [test_dns_disable_ipv6.js] [test_packaged_app_service_paths.js] [test_bug1195415.js] +[test_cookie_blacklist.js]
--- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -2525,16 +2525,19 @@ def run_test_harness(options): options.runByDir = False if mozinfo.isMac and mozinfo.info['debug']: options.runByDir = False if runner.getTestFlavor(options) == 'browser-chrome': options.runByDir = True + if runner.getTestFlavor(options) == 'chrome' and (not mozinfo.info['debug']): + options.runByDir = True + if mozinfo.info.get('buildapp') == 'mulet': options.runByDir = False result = runner.runTests(options) # don't dump failures if running from automation as treeherder already displays them if build_obj: if runner.message_logger.errors:
--- a/testing/mozharness/configs/merge_day/aurora_to_beta.py +++ b/testing/mozharness/configs/merge_day/aurora_to_beta.py @@ -1,15 +1,14 @@ config = { "log_name": "aurora_to_beta", "version_files": [ "browser/config/version.txt", "browser/config/version_display.txt", "config/milestone.txt", - "mobile/android/confvars.sh", # TODO: remove this line before gecko 43 merge "b2g/confvars.sh", ], "replacements": [ # File, from, to ("{}/{}".format(d, f), "ac_add_options --with-branding=mobile/android/branding/aurora", "ac_add_options --with-branding=mobile/android/branding/beta") for d in ["mobile/android/config/mozconfigs/android-api-11/", @@ -21,17 +20,26 @@ config = { ("{}/{}".format(d, f), "ac_add_options --with-branding=browser/branding/aurora", "ac_add_options --with-branding=browser/branding/nightly") for d in ["browser/config/mozconfigs/linux32", "browser/config/mozconfigs/linux64", "browser/config/mozconfigs/win32", "browser/config/mozconfigs/win64", "browser/config/mozconfigs/macosx64"] - for f in ["debug", "nightly", "l10n-mozconfig"] + for f in ["debug", "nightly"] + ] + [ + # File, from, to + (f, "ac_add_options --with-branding=browser/branding/aurora", "") + for f in ["browser/config/mozconfigs/linux32/l10n-mozconfig", + "browser/config/mozconfigs/linux64/l10n-mozconfig", + "browser/config/mozconfigs/win32/l10n-mozconfig", + "browser/config/mozconfigs/win64/l10n-mozconfig", + "browser/config/mozconfigs/macosx-universal/l10n-mozconfig", + "browser/config/mozconfigs/macosx64/l10n-mozconfig"] ] + [ ("browser/config/mozconfigs/macosx-universal/nightly", "ac_add_options --with-branding=browser/branding/aurora", "ac_add_options --with-branding=browser/branding/nightly"), ("browser/confvars.sh", "ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-aurora", "ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-beta,firefox-mozilla-release"), ("browser/confvars.sh",
--- a/testing/mozharness/mozharness/mozilla/building/buildb2gbase.py +++ b/testing/mozharness/mozharness/mozilla/building/buildb2gbase.py @@ -259,16 +259,28 @@ class B2GBuildBaseScript(BuildbotMixin, self.run_command(['cat', conf_file]) self.gecko_config = json.load(open(conf_file)) return self.gecko_config # The file doesn't exist; let's try loading it remotely self.gecko_config = self.query_remote_gecko_config() return self.gecko_config + def symlink_gtk3(self): + dirs = self.query_abs_dirs() + gtk3_path = os.path.join(dirs['abs_work_dir'], 'gtk3') + gtk3_symlink_path = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3') + + if os.path.isdir(gtk3_path) and not os.path.isdir(gtk3_symlink_path): + cmd = ["ln", "-sf", gtk3_path, gtk3_symlink_path] + retval = self.run_command(cmd) + if retval != 0: + self.error("failed to create symlink") + self.return_code = 2 + def query_build_env(self): """Retrieves the environment for building""" dirs = self.query_abs_dirs() gecko_config = self.load_gecko_config() env = self.query_env() for k, v in gecko_config.get('env', {}).items(): v = v.format(workdir=dirs['abs_work_dir'], srcdir=os.path.abspath(dirs['gecko_src'])) @@ -280,16 +292,23 @@ class B2GBuildBaseScript(BuildbotMixin, v = str(self.config['variant']) env['VARIANT'] = v if self.config.get('ccache'): env['CCACHE_BASEDIR'] = dirs['work_dir'] # If we get a buildid from buildbot, pass that in as MOZ_BUILD_DATE if self.buildbot_config and 'buildid' in self.buildbot_config.get('properties', {}): env['MOZ_BUILD_DATE'] = self.buildbot_config['properties']['buildid'] + self.symlink_gtk3() + env['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH') + if env['LD_LIBRARY_PATH'] is None: + env['LD_LIBRARY_PATH'] = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib') + else: + env['LD_LIBRARY_PATH'] += ':%s' % os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib') + return env def query_hgweb_url(self, repo, rev, filename=None): if filename: url = "{baseurl}/raw-file/{rev}/{filename}".format( baseurl=repo, rev=rev, filename=filename)
--- a/testing/mozharness/scripts/b2g_build.py +++ b/testing/mozharness/scripts/b2g_build.py @@ -536,45 +536,27 @@ class B2GBuild(LocalesMixin, PurgeMixin, else: # Ensure we always utilize the correct number of cores # regardless of the configuration which may be set by repo # config changes... cmd.append('-j{0}'.format(multiprocessing.cpu_count())) cmd.append(target) return cmd - def symlink_gtk3(self): - dirs = self.query_abs_dirs() - gtk3_path = os.path.join(dirs['abs_work_dir'], 'gtk3') - gtk3_symlink_path = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3') - - if os.path.isdir(gtk3_path): - cmd = ["ln", "-s", gtk3_path, gtk3_symlink_path] - retval = self.run_command(cmd) - if retval != 0: - self.error("failed to create symlink") - self.return_code = 2 - def build(self): dirs = self.query_abs_dirs() gecko_config = self.load_gecko_config() build_targets = gecko_config.get('build_targets', []) build_targets.extend(self.config.get("build_targets", [])) if not build_targets: cmds = [self.generate_build_command()] else: cmds = [self.generate_build_command(t) for t in build_targets] - self.symlink_gtk3() env = self.query_build_env() - env['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH') - if env['LD_LIBRARY_PATH'] is None: - env['LD_LIBRARY_PATH'] = os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib') - else: - env['LD_LIBRARY_PATH'] += ':%s' % os.path.join(dirs['abs_work_dir'], 'gecko', 'gtk3', 'usr', 'local', 'lib') if self.config.get('gaia_languages_file'): env['LOCALE_BASEDIR'] = dirs['gaia_l10n_base_dir'] env['LOCALES_FILE'] = os.path.join(dirs['abs_work_dir'], 'gaia', self.config['gaia_languages_file']) if self.config.get('locales_file'): env['L10NBASEDIR'] = dirs['abs_l10n_dir'] env['MOZ_CHROME_MULTILOCALE'] = " ".join(self.query_locales()) if 'PATH' not in env: env['PATH'] = os.environ.get('PATH')
--- a/testing/specialpowers/content/SpecialPowersObserverAPI.js +++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js @@ -568,17 +568,17 @@ SpecialPowersObserverAPI.prototype = { .QueryInterface(Ci.nsISubstitutingProtocolHandler); let resURI = Services.io.newURI(target, null, null); let uri = Services.io.newURI(resourceHandler.resolveURI(resURI), null, null); extension = new Extension({ id, resourceURI: uri }); } else { - extension = Extension.generate(ext); + extension = Extension.generate(id, ext); } let resultListener = (...args) => { this._sendReply(aMessage, "SPExtensionMessage", {id, type: "testResult", args}); }; let messageListener = (...args) => { args.shift();
--- a/testing/specialpowers/content/specialpowersAPI.js +++ b/testing/specialpowers/content/specialpowersAPI.js @@ -2055,16 +2055,18 @@ SpecialPowersAPI.prototype = { }); let unloadPromise = new Promise(resolve => { resolveUnload = resolve; }); handler = Cu.waiveXrays(handler); ext = Cu.waiveXrays(ext); let sp = this; let extension = { + id, + startup() { sp._sendAsyncMessage("SPStartupExtension", {id}); return startupPromise; }, unload() { sp._sendAsyncMessage("SPUnloadExtension", {id}); return unloadPromise; @@ -2092,13 +2094,17 @@ SpecialPowersAPI.prototype = { dump(`Unexpected: ${msg.data.type}\n`); } } }; this._addMessageListener("SPExtensionMessage", listener); return extension; }, + + invalidateExtensionStorageCache: function() { + this.notifyObserversInParentProcess(null, "extension-invalidate-storage-cache", ""); + }, }; this.SpecialPowersAPI = SpecialPowersAPI; this.bindDOMWindowUtils = bindDOMWindowUtils; this.getRawComponents = getRawComponents;
--- a/testing/talos/talos.json +++ b/testing/talos/talos.json @@ -9,38 +9,30 @@ "suites": { "chromez": { "tests": ["tresize", "tcanvasmark"] }, "chromez-e10s": { "tests": ["tresize", "tcanvasmark"], "talos_options": ["--e10s"] }, - "chromez-osx-e10s": { - "tests": ["tresize", "tcanvasmark"], - "talos_options": ["--e10s"] - }, "dromaeojs": { "tests": ["dromaeo_css", "kraken", "v8_7"] }, "dromaeojs-e10s": { "tests": ["dromaeo_css", "kraken", "v8_7"], "talos_options": ["--e10s"] }, "other": { "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"] }, "other-e10s": { "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"], "talos_options": ["--e10s"] }, - "other-osx-e10s": { - "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"], - "talos_options": ["--e10s"] - }, "other_nol64": { "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"] }, "other-e10s_nol64": { "tests": ["a11yr", "ts_paint", "tpaint", "sessionrestore", "sessionrestore_no_auto_restore"], "talos_options": ["--e10s"] }, "other_l64": { @@ -66,51 +58,42 @@ "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", "pagesets_parent_dir_path": "talos/page_load_test/", "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest", "plugins": { "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip", "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip" } }, - "g1-osx-e10s": { - "tests": ["tp5o_scroll", "glterrain"], - "talos_options": ["--e10s"] - }, "g2": { "tests": ["damp", "tps"], "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", "pagesets_parent_dir_path": "talos/page_load_test/", "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest" }, "g2-e10s": { "tests": ["damp", "tps"], "talos_options": ["--e10s"], "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", "pagesets_parent_dir_path": "talos/page_load_test/", "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest" }, - "g2-osx-e10s": { - "tests": ["damp", "tps"], - "talos_options": ["--e10s"], - "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", - "pagesets_parent_dir_path": "talos/page_load_test/", - "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest" + "g3": { + "tests": ["dromaeo_dom"] + }, + "g3-e10s": { + "tests": ["dromaeo_dom"] }, "svgr": { "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart"] }, "svgr-e10s": { "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart"], "talos_options": ["--e10s"] }, - "svgr-osx-e10s": { - "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "cart"], - "talos_options": ["--e10s"] - }, "tp5o": { "tests": ["tp5o"], "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", "pagesets_parent_dir_path": "talos/page_load_test/", "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest", "plugins": { "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip", "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip" @@ -122,27 +105,16 @@ "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", "pagesets_parent_dir_path": "talos/page_load_test/", "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest", "plugins": { "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip", "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip" } }, - "tp5o-osx-e10s": { - "tests": ["tp5o"], - "talos_options": ["--e10s"], - "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", - "pagesets_parent_dir_path": "talos/page_load_test/", - "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5o.manifest", - "plugins": { - "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip", - "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip" - } - }, "xperf": { "tests": ["tp5n"], "pagesets_url": "http://talos-bundles.pvt.build.mozilla.org/zips/tp5n.zip", "pagesets_parent_dir_path": "talos/page_load_test/", "pagesets_manifest_path": "talos/page_load_test/tp5n/tp5n.manifest", "plugins": { "32": "http://talos-bundles.pvt.build.mozilla.org/zips/flash32_10_3_183_5.zip", "64": "http://talos-bundles.pvt.build.mozilla.org/zips/flash64_11_0_d1_98.zip"
--- a/testing/testsuite-targets.mk +++ b/testing/testsuite-targets.mk @@ -282,17 +282,16 @@ xpcshell-tests: -I$(DEPTH)/build \ -I$(topsrcdir)/build \ -I$(DEPTH)/_tests/mozbase/mozinfo \ $(topsrcdir)/testing/xpcshell/runxpcshelltests.py \ --manifest=$(DEPTH)/_tests/xpcshell/xpcshell.ini \ --build-info-json=$(DEPTH)/mozinfo.json \ --no-logfiles \ --test-plugin-path='$(DIST)/plugins' \ - --tests-root-dir=$(abspath _tests/xpcshell) \ --testing-modules-dir=$(abspath _tests/modules) \ $(SYMBOLS_PATH) \ $(TEST_PATH_ARG) $(EXTRA_TEST_ARGS) \ $(xpcshell_path) B2G_XPCSHELL = \ rm -f ./@.log && \ $(PYTHON) -u $(topsrcdir)/config/pythonpath.py \
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/unregister-then-register-new-script.https.html.ini +++ b/testing/web-platform/mozilla/meta/service-workers/service-worker/unregister-then-register-new-script.https.html.ini @@ -1,27 +1,8 @@ [unregister-then-register-new-script.https.html] - type: testharness - disabled: - if e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1205675 - expected: - if debug and not e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86_64") and (bits == 64): CRASH - if debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): CRASH - if not debug and e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86") and (bits == 32): OK - if not debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): OK - if not debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): OK - if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): OK - if debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): CRASH - if not debug and not e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86") and (bits == 32): OK - if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): OK - if not debug and not e10s and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): OK - if not debug and not e10s and (os == "win") and (version == "6.2.9200") and (processor == "x86_64") and (bits == 64): OK - if not debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): OK - if debug and not e10s and (os == "win") and (version == "6.2.9200") and (processor == "x86_64") and (bits == 64): CRASH - if debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): CRASH - if debug and not e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): CRASH - TIMEOUT + [Registering a new script URL while an unregistered registration is in use] expected: FAIL [Registering a new script URL that fails to install does not resurrect an unregistered registration] expected: FAIL
--- a/toolkit/components/extensions/Extension.jsm +++ b/toolkit/components/extensions/Extension.jsm @@ -273,17 +273,19 @@ var GlobalManager = { let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIWebNavigation) .QueryInterface(Ci.nsIDocShellTreeItem) .sameTypeRootTreeItem .QueryInterface(Ci.nsIDocShell); if (this.docShells.has(docShell)) { let {extension, context} = this.docShells.get(docShell); - inject(extension, context); + if (context) { + inject(extension, context); + } return; } // We don't inject into sub-frames of a UI page. if (contentWindow != contentWindow.top) { return; } @@ -362,17 +364,17 @@ this.Extension = function(addonData) * If a manifest file is provided here, it takes precedence over * a generated one. Always use "/" as a directory separator. * Directories should appear here only implicitly (as a prefix * to file names) * * To make things easier, the value of "background" and "files"[] can * be a function, which is converted to source that is run. */ -this.Extension.generate = function(data) +this.Extension.generate = function(id, data) { let manifest = data.manifest; if (!manifest) { manifest = {}; } let files = data.files; if (!files) { @@ -387,19 +389,17 @@ this.Extension.generate = function(data) } else { if (!(keys[0] in obj)) { obj[keys[0]] = {}; } provide(obj[keys[0]], keys.slice(1), value, override); } } - let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); - let uuid = uuidGenerator.generateUUID().number; - provide(manifest, ["applications", "gecko", "id"], uuid); + provide(manifest, ["applications", "gecko", "id"], id); provide(manifest, ["name"], "Generated extension"); provide(manifest, ["manifest_version"], 2); provide(manifest, ["version"], "1.0"); if (data.background) { let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); let bgScript = uuidGenerator.generateUUID().number + ".js"; @@ -453,17 +453,17 @@ this.Extension.generate = function(data) flushJarCache(file); Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path}); let fileURI = Services.io.newFileURI(file); let jarURI = Services.io.newURI("jar:" + fileURI.spec + "!/", null, null); return new Extension({ - id: uuid, + id, resourceURI: jarURI, cleanupFile: file }); } Extension.prototype = { on(hook, f) { return this.emitter.on(hook, f); @@ -633,16 +633,30 @@ Extension.prototype = { let locale = Locale.findClosestLocale(locales); if (locale) { return this.readLocaleFile(locale.name).catch(() => {}); } return {}; }, + broadcast(msg, data) { + return new Promise(resolve => { + let count = Services.ppmm.childCount; + Services.ppmm.addMessageListener(msg + "Complete", function listener() { + count--; + if (count == 0) { + Services.ppmm.removeMessageListener(msg + "Complete", listener); + resolve(); + } + }); + Services.ppmm.broadcastAsyncMessage(msg, data); + }); + }, + runManifest(manifest) { let permissions = manifest.permissions || []; let webAccessibleResources = manifest.web_accessible_resources || []; let whitelist = []; for (let perm of permissions) { if (perm.match(/:\/\//)) { whitelist.push(perm); @@ -663,17 +677,18 @@ Extension.prototype = { } let data = Services.ppmm.initialProcessData; if (!data["Extension:Extensions"]) { data["Extension:Extensions"] = []; } let serial = this.serialize(); data["Extension:Extensions"].push(serial); - Services.ppmm.broadcastAsyncMessage("Extension:Startup", serial); + + return this.broadcast("Extension:Startup", serial); }, callOnClose(obj) { this.onShutdown.add(obj); }, forgetOnClose(obj) { this.onShutdown.delete(obj); @@ -693,17 +708,17 @@ Extension.prototype = { GlobalManager.init(this); this.manifest = manifest; this.localeMessages = messages; Management.emit("startup", this); - this.runManifest(manifest); + return this.runManifest(manifest); }).catch(e => { dump(`Extension error: ${e} ${e.fileName}:${e.lineNumber}\n`); Cu.reportError(e); throw e; }); }, cleanupGeneratedFile() { @@ -711,29 +726,23 @@ Extension.prototype = { return; } let file = this.cleanupFile; this.cleanupFile = null; Services.obs.removeObserver(this, "xpcom-shutdown"); - let count = Services.ppmm.childCount; - - Services.ppmm.addMessageListener("Extension:FlushJarCacheComplete", function listener() { - count--; - if (count == 0) { - // We can't delete this file until everyone using it has - // closed it (because Windows is dumb). So we wait for all the - // child processes (including the parent) to flush their JAR - // caches. These caches may keep the file open. - file.remove(false); - } + this.broadcast("Extension:FlushJarCache", {path: file.path}).then(() => { + // We can't delete this file until everyone using it has + // closed it (because Windows is dumb). So we wait for all the + // child processes (including the parent) to flush their JAR + // caches. These caches may keep the file open. + file.remove(false); }); - Services.ppmm.broadcastAsyncMessage("Extension:FlushJarCache", {path: file.path}); }, shutdown() { this.hasShutdown = true; if (!this.manifest) { return; }
--- a/toolkit/components/extensions/ExtensionContent.jsm +++ b/toolkit/components/extensions/ExtensionContent.jsm @@ -25,17 +25,17 @@ XPCOMUtils.defineLazyModuleGetter(this, "resource://gre/modules/ExtensionManagement.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", "resource://gre/modules/MatchPattern.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { - runSafeWithoutClone, + runSafeSyncWithoutClone, MessageBroker, Messenger, ignoreEvent, injectAPI, flushJarCache, } = ExtensionUtils; function isWhenBeforeOrSame(when1, when2) @@ -46,26 +46,30 @@ function isWhenBeforeOrSame(when1, when2 return table[when1] <= table[when2]; } // This is the fairly simple API that we inject into content // scripts. var api = context => { return { runtime: { connect: function(extensionId, connectInfo) { + if (!connectInfo) { + connectInfo = extensionId; + extensionId = null; + } let name = connectInfo && connectInfo.name || ""; let recipient = extensionId ? {extensionId} : {extensionId: context.extensionId}; return context.messenger.connect(context.messageManager, name, recipient); }, getManifest: function(context) { return context.extension.getManifest(); }, - getURL: function(path) { + getURL: function(url) { return context.extension.baseURI.resolve(url); }, onConnect: context.messenger.onConnect("runtime.onConnect"), onMessage: context.messenger.onMessage("runtime.onMessage"), sendMessage: function(...args) { @@ -79,17 +83,17 @@ var api = context => { return { } let recipient = extensionId ? {extensionId} : {extensionId: context.extensionId}; context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback); }, }, extension: { - getURL: function(path) { + getURL: function(url) { return context.extension.baseURI.resolve(url); }, inIncognitoContext: PrivateBrowsingUtils.isContentWindowPrivate(context.contentWindow), }, }}; // Represents a content script. @@ -132,22 +136,22 @@ Script.prototype = { } if (shouldRun("document_start")) { let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor). getInterface(Ci.nsIDOMWindowUtils); for (let url of this.css) { url = extension.baseURI.resolve(url); - runSafeWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET); + runSafeSyncWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET); } if (this.options.cssCode) { let url = "data:text/css;charset=utf-8," + encodeURIComponent(this.options.cssCode); - runSafeWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET); + runSafeSyncWithoutClone(winUtils.loadSheetUsingURIString, url, winUtils.AUTHOR_SHEET); } } let scheduled = this.run_at || "document_idle"; if (shouldRun(scheduled)) { for (let url of this.js) { // On gonk we need to load the resources asynchronously because the // app: channels only support asyncOpen. This is safe only in the @@ -157,17 +161,17 @@ Script.prototype = { } url = extension.baseURI.resolve(url); let options = { target: sandbox, charset: "UTF-8", async: AppConstants.platform == "gonk" } - Services.scriptloader.loadSubScriptWithOptions(url, options); + runSafeSyncWithoutClone(Services.scriptloader.loadSubScriptWithOptions, url, options); } if (this.options.jsCode) { Cu.evalInSandbox(this.options.jsCode, sandbox, "latest"); } } }, }; @@ -189,16 +193,18 @@ function getWindowMessageManager(content // Cu.Sandbox to run the code. There is a separate scope for each // frame. function ExtensionContext(extensionId, contentWindow) { this.extension = ExtensionManager.get(extensionId); this.extensionId = extensionId; this.contentWindow = contentWindow; + this.onClose = new Set(); + let utils = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); let outerWindowId = utils.outerWindowID; let frameId = contentWindow == contentWindow.top ? 0 : outerWindowId; this.frameId = frameId; let mm = getWindowMessageManager(contentWindow); this.messageManager = mm; @@ -223,18 +229,16 @@ function ExtensionContext(extensionId, c {id: extensionId, frameId}, delegate); let chromeObj = Cu.createObjectIn(this.sandbox, {defineAs: "browser"}); // Sandboxes don't get Xrays for some weird compatibility // reason. However, we waive here anyway in case that changes. Cu.waiveXrays(this.sandbox).chrome = Cu.waiveXrays(this.sandbox).browser; injectAPI(api(this), chromeObj); - - this.onClose = new Set(); } ExtensionContext.prototype = { get cloneScope() { return this.sandbox; }, execute(script, shouldRun) { @@ -478,16 +482,17 @@ var ExtensionManager = { receiveMessage({name, data}) { let extension; switch (name) { case "Extension:Startup": { extension = new BrowserExtensionContent(data); this.extensions.set(data.id, extension); DocumentManager.startupExtension(data.id); + Services.cpmm.sendAsyncMessage("Extension:StartupComplete"); break; } case "Extension:Shutdown": { extension = this.extensions.get(data.id); extension.shutdown(); DocumentManager.shutdownExtension(data.id); this.extensions.delete(data.id);
--- a/toolkit/components/extensions/ExtensionStorage.jsm +++ b/toolkit/components/extensions/ExtensionStorage.jsm @@ -90,17 +90,23 @@ this.ExtensionStorage = { return this.write(extensionId); }); }, remove(extensionId, items) { return this.read(extensionId).then(extData => { let changes = {}; - for (let prop in items) { + if (Array.isArray(items)) { + for (let prop of items) { + changes[prop] = {oldValue: extData[prop]}; + delete extData[prop]; + } + } else { + let prop = items; changes[prop] = {oldValue: extData[prop]}; delete extData[prop]; } let listeners = this.listeners.get(extensionId); if (listeners) { for (let listener of listeners) { listener(changes); @@ -111,29 +117,34 @@ this.ExtensionStorage = { }); }, get(extensionId, keys) { return this.read(extensionId).then(extData => { let result = {}; if (keys === null) { Object.assign(result, extData); - } else if (typeof(keys) == "object") { + } else if (typeof(keys) == "object" && !Array.isArray(keys)) { for (let prop in keys) { if (prop in extData) { result[prop] = extData[prop]; } else { result[prop] = keys[prop]; } } } else if (typeof(keys) == "string") { - result[prop] = extData[prop] || undefined; + let prop = keys; + if (prop in extData) { + result[prop] = extData[prop]; + } } else { for (let prop of keys) { - result[prop] = extData[prop] || undefined; + if (prop in extData) { + result[prop] = extData[prop]; + } } } return result; }); }, addOnChangedListener(extensionId, listener) { @@ -141,9 +152,25 @@ this.ExtensionStorage = { listeners.add(listener); this.listeners.set(extensionId, listeners); }, removeOnChangedListener(extensionId, listener) { let listeners = this.listeners.get(extensionId); listeners.delete(listener); }, + + init() { + Services.obs.addObserver(this, "extension-invalidate-storage-cache", false); + Services.obs.addObserver(this, "xpcom-shutdown", false); + }, + + observe(subject, topic, data) { + if (topic == "xpcom-shutdown") { + Services.obs.removeObserver(this, "extension-invalidate-storage-cache"); + Services.obs.removeObserver(this, "xpcom-shutdown"); + } else if (topic == "extension-invalidate-storage-cache") { + this.cache.clear(); + } + }, }; + +ExtensionStorage.init();
--- a/toolkit/components/extensions/ExtensionUtils.jsm +++ b/toolkit/components/extensions/ExtensionUtils.jsm @@ -10,26 +10,51 @@ const Ci = Components.interfaces; const Cc = Components.classes; const Cu = Components.utils; const Cr = Components.results; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); // Run a function and report exceptions. -function runSafeWithoutClone(f, ...args) +function runSafeSyncWithoutClone(f, ...args) { try { return f(...args); } catch (e) { - dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n${e.stack}\n${Error().stack}`); + dump(`Extension error: ${e} ${e.fileName} ${e.lineNumber}\n[[Exception stack\n${e.stack}Current stack\n${Error().stack}]]\n`); Cu.reportError(e); } } +// Run a function and report exceptions. +function runSafeWithoutClone(f, ...args) +{ + if (typeof(f) != "function") { + dump(`Extension error: expected function\n${Error().stack}`); + return; + } + + Services.tm.currentThread.dispatch(function() { + runSafeSyncWithoutClone(f, ...args); + }, Ci.nsIEventTarget.DISPATCH_NORMAL); +} + +// Run a function, cloning arguments into context.cloneScope, and +// report exceptions. |f| is expected to be in context.cloneScope. +function runSafeSync(context, f, ...args) +{ + try { + args = Cu.cloneInto(args, context.cloneScope); + } catch (e) { + dump(`runSafe failure\n${context.cloneScope}\n${Error().stack}`); + } + return runSafeSyncWithoutClone(f, ...args); +} + // Run a function, cloning arguments into context.cloneScope, and // report exceptions. |f| is expected to be in context.cloneScope. function runSafe(context, f, ...args) { try { args = Cu.cloneInto(args, context.cloneScope); } catch (e) { dump(`runSafe failure\n${context.cloneScope}\n${Error().stack}`); @@ -91,16 +116,21 @@ function EventManager(context, name, reg this.register = register; this.unregister = null; this.callbacks = new Set(); this.registered = false; } EventManager.prototype = { addListener(callback) { + if (typeof(callback) != "function") { + dump(`Expected function\n${Error().stack}`); + return; + } + if (!this.registered) { this.context.callOnClose(this); let fireFunc = this.fire.bind(this); let fireWithoutClone = this.fireWithoutClone.bind(this); fireFunc.withoutClone = fireWithoutClone; this.unregister = this.register(fireFunc); } @@ -127,17 +157,17 @@ EventManager.prototype = { fire(...args) { for (let callback of this.callbacks) { runSafe(this.context, callback, ...args); } }, fireWithoutClone(...args) { for (let callback of this.callbacks) { - runSafeWithoutClone(callback, ...args); + runSafeSyncWithoutClone(callback, ...args); } }, close() { this.unregister(); }, api() { @@ -335,16 +365,19 @@ function Port(context, messageManager, n this.context = context; this.messageManager = messageManager; this.name = name; this.id = id; this.listenerName = `Extension:Port-${this.id}`; this.disconnectName = `Extension:Disconnect-${this.id}`; this.sender = sender; this.disconnected = false; + + this.messageManager.addMessageListener(this.disconnectName, this, true); + this.disconnectListeners = new Set(); } Port.prototype = { api() { let portObj = Cu.createObjectIn(this.context.cloneScope); // We want a close() notification when the window is destroyed. this.context.callOnClose(this); @@ -362,19 +395,19 @@ Port.prototype = { }, onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => { let listener = () => { if (!this.disconnected) { fire(); } }; - this.messageManager.addMessageListener(this.disconnectName, listener, true); + this.disconnectListeners.add(listener); return () => { - this.messageManager.removeMessageListener(this.disconnectName, listener); + this.disconnectListeners.delete(listener); }; }).api(), onMessage: new EventManager(this.context, "Port.onMessage", fire => { let listener = ({data}) => { if (!this.disconnected) { fire(data); } }; @@ -389,19 +422,41 @@ Port.prototype = { if (this.sender) { publicAPI.sender = this.sender; } injectAPI(publicAPI, portObj); return portObj; }, - disconnect() { + handleDisconnection() { + this.messageManager.removeMessageListener(this.disconnectName, this); this.context.forgetOnClose(this); - this.disconnect = true; + this.disconnected = true; + }, + + receiveMessage(msg) { + if (msg.name == this.disconnectName) { + if (this.disconnected) { + return; + } + + for (let listener of this.disconnectListeners) { + listener(); + } + + this.handleDisconnection(); + } + }, + + disconnect() { + if (this.disconnected) { + throw "Attempt to disconnect() a disconnected port"; + } + this.handleDisconnection(); this.messageManager.sendAsyncMessage(this.disconnectName); }, close() { this.disconnect(); }, }; @@ -457,17 +512,17 @@ Messenger.prototype = { }; if (responseCallback) { messageManager.addMessageListener(replyName, listener); this.context.callOnClose(onClose); } }, onMessage(name) { - return new EventManager(this.context, name, fire => { + return new SingletonEventManager(this.context, name, callback => { let listener = (type, target, message, sender, recipient) => { message = Cu.cloneInto(message, this.context.cloneScope); if (this.delegate) { this.delegate.getSender(this.context, target, sender); } sender = Cu.cloneInto(sender, this.context.cloneScope); let mm = getMessageManager(target); @@ -478,22 +533,22 @@ Messenger.prototype = { if (!valid) { return; } sent = true; mm.sendAsyncMessage(replyName, {data, gotData: true}); }; sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope); - let result = fire.withoutClone(message, sender, sendResponse); + let result = runSafeSyncWithoutClone(callback, message, sender, sendResponse); if (result !== true) { valid = false; - } - if (!sent) { - mm.sendAsyncMessage(replyName, {gotData: false}); + if (!sent) { + mm.sendAsyncMessage(replyName, {gotData: false}); + } } }; this.broker.addListener("message", listener, this.filter); return () => { this.broker.removeListener("message", listener); }; }).api(); @@ -529,17 +584,19 @@ Messenger.prototype = { function flushJarCache(jarFile) { Services.obs.notifyObservers(jarFile, "flush-cache-entry", null); } this.ExtensionUtils = { runSafeWithoutClone, + runSafeSyncWithoutClone, runSafe, + runSafeSync, DefaultWeakMap, EventManager, SingletonEventManager, ignoreEvent, injectAPI, MessageBroker, Messenger, flushJarCache,
--- a/toolkit/components/extensions/ext-alarms.js +++ b/toolkit/components/extensions/ext-alarms.js @@ -4,17 +4,17 @@ Cu.import("resource://gre/modules/Extens var { EventManager, ignoreEvent, } = ExtensionUtils; // WeakMap[Extension -> Set[Alarm]] var alarmsMap = new WeakMap(); -// WeakMap[Extension -> callback] +// WeakMap[Extension -> Set[callback]] var alarmCallbacksMap = new WeakMap(); // Manages an alarm created by the extension (alarms API). function Alarm(extension, name, alarmInfo) { this.extension = extension; this.name = name; this.when = alarmInfo.when; @@ -41,18 +41,18 @@ function Alarm(extension, name, alarmInf Alarm.prototype = { clear() { this.timer.cancel(); alarmsMap.get(this.extension).delete(this); this.canceled = true; }, observe(subject, topic, data) { - if (alarmCallbacksMap.has(this.extension)) { - alarmCallbacksMap.get(this.extension)(this); + for (let callback in alarmCallbacksMap.get(this.extension)) { + callback(this); } if (this.canceled) { return; } if (!this.periodInMinutes) { this.clear(); return; @@ -69,23 +69,25 @@ Alarm.prototype = { scheduledTime: this.scheduledTime, periodInMinutes: this.periodInMinutes, }; }, }; extensions.on("startup", (type, extension) => { alarmsMap.set(extension, new Set()); + alarmCallbacksMap.set(extension, new Set()); }); extensions.on("shutdown", (type, extension) => { for (let alarm of alarmsMap.get(extension)) { alarm.clear(); } alarmsMap.delete(extension); + alarmCallbacksMap.delete(extension); }); extensions.registerAPI((extension, context) => { return { alarms: { create: function(...args) { let name = "", alarmInfo; if (args.length == 1) { @@ -155,16 +157,16 @@ extensions.registerAPI((extension, conte } }, onAlarm: new EventManager(context, "alarms.onAlarm", fire => { let callback = alarm => { fire(alarm.data); }; - alarmCallbacksMap.set(extension, callback); + alarmCallbacksMap.get(extension).add(callback); return () => { - alarmCallbacksMap.delete(extension); + alarmCallbacksMap.get(extension).delete(callback); }; }).api(), }, }; });
--- a/toolkit/components/extensions/ext-extension.js +++ b/toolkit/components/extensions/ext-extension.js @@ -1,10 +1,32 @@ extensions.registerAPI((extension, context) => { return { extension: { getURL: function(url) { return extension.baseURI.resolve(url); }, + + getViews: function(fetchProperties) { + let result = Cu.cloneInto([], context.cloneScope); + + for (let view of extension.views) { + if (fetchProperties && "type" in fetchProperties) { + if (view.type != fetchProperties.type) { + continue; + } + } + + if (fetchProperties && "windowId" in fetchProperties) { + if (view.windowId != fetchProperties.windowId) { + continue; + } + } + + result.push(view.contentWindow); + } + + return result; + }, }, }; });
--- a/toolkit/components/extensions/ext-notifications.js +++ b/toolkit/components/extensions/ext-notifications.js @@ -4,17 +4,17 @@ Cu.import("resource://gre/modules/Extens var { EventManager, ignoreEvent, } = ExtensionUtils; // WeakMap[Extension -> Set[Notification]] var notificationsMap = new WeakMap(); -// WeakMap[Extension -> callback] +// WeakMap[Extension -> Set[callback]] var notificationCallbacksMap = new WeakMap(); // Manages a notification popup (notifications API) created by the extension. function Notification(extension, id, options) { this.extension = extension; this.id = id; this.options = options; @@ -49,33 +49,35 @@ Notification.prototype = { notificationsMap.get(this.extension).delete(this); }, observe(subject, topic, data) { if (topic != "alertfinished") { return; } - if (notificationCallbacksMap.has(this.extension)) { - notificationCallbackMap.get(this.extension)(this); + for (let callback in notificationCallbacksMap.get(this.extension)) { + callback(this); } notificationsMap.get(this.extension).delete(this); }, }; extensions.on("startup", (type, extension) => { notificationsMap.set(extension, new Set()); + notificationCallbacksMap.set(extension, new Set()); }); extensions.on("shutdown", (type, extension) => { for (let notification of notificationsMap.get(extension)) { notification.clear(); } notificationsMap.delete(extension); + notificationCallbacksMap.delete(extension); }); var nextId = 0; extensions.registerPrivilegedAPI("notifications", (extension, context) => { return { notifications: { create: function(...args) { @@ -123,19 +125,19 @@ extensions.registerPrivilegedAPI("notifi }, onClosed: new EventManager(context, "notifications.onClosed", fire => { let listener = notification => { // FIXME: Support the byUser argument. fire(notification.id, true); }; - notificationCallbackMap.set(extension, listener); + notificationCallbacksMap.get(extension).add(listener); return () => { - notificationCallbackMap.delete(extension); + notificationCallbacksMap.get(extension).delete(listener); }; }).api(), // FIXME onButtonClicked: ignoreEvent(), onClicked: ignoreEvent(), }, };
--- a/toolkit/components/extensions/ext-test.js +++ b/toolkit/components/extensions/ext-test.js @@ -1,19 +1,28 @@ Components.utils.import("resource://gre/modules/ExtensionUtils.jsm"); var { EventManager, } = ExtensionUtils; +// WeakMap[Extension -> Set(callback)] var messageHandlers = new WeakMap(); +extensions.on("startup", (type, extension) => { + messageHandlers.set(extension, new Set()); +}); + +extensions.on("shutdown", (type, extension) => { + messageHandlers.delete(extension); +}); + extensions.on("test-message", (type, extension, ...args) => { - let fire = messageHandlers.get(extension); - if (fire) { - fire(...args); + let handlers = messageHandlers.get(extension); + for (let handler of handlers) { + handler(...args); } }); extensions.registerAPI((extension, context) => { return { test: { sendMessage: function(...args) { extension.emit("test-message", ...args); @@ -47,16 +56,18 @@ extensions.registerAPI((extension, conte extension.emit("test-result", !value ? true : false, msg); }, assertEq: function(expected, actual, msg) { extension.emit("test-eq", expected === actual, msg, String(expected), String(actual)); }, onMessage: new EventManager(context, "test.onMessage", fire => { - messageHandlers.set(extension, fire); + let handlers = messageHandlers.get(extension); + handlers.add(fire); + return () => { - messageHandlers.delete(extension); + handlers.delete(fire); }; }).api(), }, }; });
--- a/toolkit/components/extensions/ext-webNavigation.js +++ b/toolkit/components/extensions/ext-webNavigation.js @@ -40,17 +40,17 @@ function WebNavigationEventManager(conte // Fills in tabId typically. let result = {}; extensions.emit("fill-browser-data", data.browser, data2, result); if (result.cancel) { return; } - return runSafe(context, callback, data2); + runSafe(context, callback, data2); }; WebNavigation[eventName].addListener(listener); return () => { WebNavigation[eventName].removeListener(listener); }; };
--- a/toolkit/components/extensions/ext-webRequest.js +++ b/toolkit/components/extensions/ext-webRequest.js @@ -5,17 +5,17 @@ Cu.import("resource://gre/modules/XPCOMU XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern", "resource://gre/modules/MatchPattern.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "WebRequest", "resource://gre/modules/WebRequest.jsm"); Cu.import("resource://gre/modules/ExtensionUtils.jsm"); var { SingletonEventManager, - runSafe, + runSafeSync, } = ExtensionUtils; // EventManager-like class specifically for WebRequest. Inherits from // SingletonEventManager. Takes care of converting |details| parameter // when invoking listeners. function WebRequestEventManager(context, eventName) { let name = `webRequest.${eventName}`; @@ -48,17 +48,17 @@ function WebRequestEventManager(context, let optional = ["requestHeaders", "responseHeaders", "statusCode"]; for (let opt of optional) { if (opt in data) { data2[opt] = data[opt]; } } - return runSafe(context, callback, data2); + return runSafeSync(context, callback, data2); }; let filter2 = {}; filter2.urls = new MatchPattern(filter.urls); if (filter.types) { filter2.types = filter.types; } if (filter.tabId) {
--- a/toolkit/components/extensions/test/extensions/content_script/manifest.json +++ b/toolkit/components/extensions/test/extensions/content_script/manifest.json @@ -1,32 +1,32 @@ { "name": "Content script extension test", "version": "1.0", "manifest_version": 2, "description": "", "content_scripts": [ { - "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"], + "matches": ["http://mochi.test/*/file_sample.html"], "js": ["content_script_start.js"], "run_at": "document_start" }, { - "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"], + "matches": ["http://mochi.test/*/file_sample.html"], "js": ["content_script_end.js"], "run_at": "document_end" }, { - "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"], + "matches": ["http://mochi.test/*/file_sample.html"], "js": ["content_script_idle.js"], "run_at": "document_idle" }, { - "matches": ["http://mochi.test/tests/toolkit/components/extensions/test/mochitest/file_contentscript_*.html"], + "matches": ["http://mochi.test/*/file_sample.html"], "js": ["content_script.js"], "run_at": "document_idle" } ], "background": { "scripts": ["background.js"] }
rename from toolkit/components/extensions/test/mochitest/file_contentscript_page1.html rename to toolkit/components/extensions/test/mochitest/file_sample.html
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/head.js @@ -0,0 +1,8 @@ +function waitForLoad(win) { + return new Promise(resolve => { + win.addEventListener("load", function listener() { + win.removeEventListener("load", listener, true); + resolve(); + }, true); + }); +}
--- a/toolkit/components/extensions/test/mochitest/mochitest.ini +++ b/toolkit/components/extensions/test/mochitest/mochitest.ini @@ -1,22 +1,29 @@ [DEFAULT] -skip-if = os == 'android' || buildapp == 'b2g' || os == 'mac' +skip-if = os == 'android' || buildapp == 'b2g' || buildapp == 'mulet' || os == 'mac' || asan support-files = + head.js file_WebRequest_page1.html file_WebRequest_page2.html file_image_good.png file_image_bad.png file_image_redirect.png file_style_good.css file_style_bad.css file_style_redirect.css file_script_good.js file_script_bad.js file_script_redirect.js file_script_xhr.js - file_contentscript_page1.html + file_sample.html -[test_simple_extensions.html] -[test_extension_contentscript.html] -[test_extension_webrequest.html] -[test_generate_extension.html] -[test_sandbox_var.html] +[test_ext_simple.html] +[test_ext_geturl.html] +[test_ext_contentscript.html] +[test_ext_webrequest.html] +[test_ext_generate.html] +[test_ext_runtime_connect.html] +[test_ext_runtime_disconnect.html] +[test_ext_sandbox_var.html] +[test_ext_sendmessage_reply.html] +[test_ext_sendmessage_doublereply.html] +[test_ext_storage.html]
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_contentscript.html @@ -0,0 +1,51 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for content script</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +add_task(function* test_contentscript() +{ + let extension = ExtensionTestUtils.loadExtension("content_script"); + yield extension.startup(); + info("extension loaded"); + + let loadingCount = 0; + let interactiveCount = 0; + let completeCount = 0; + extension.onMessage("script-run-loading", () => { loadingCount++; }); + extension.onMessage("script-run-interactive", () => { interactiveCount++; }); + + let completePromise = new Promise(resolve => { + extension.onMessage("script-run-complete", () => { completeCount++; resolve(); }); + }); + + let chromeNamespacePromise = extension.awaitMessage("chrome-namespace-ok"); + + let win = window.open("file_sample.html"); + + yield Promise.all([waitForLoad(win), completePromise, chromeNamespacePromise]); + info("test page loaded"); + + win.close(); + + is(loadingCount, 1, "document_start script ran exactly once"); + is(interactiveCount, 1, "document_end script ran exactly once"); + is(completeCount, 1, "document_idle script ran exactly once"); + + yield extension.unload(); + info("extension unloaded"); +}); +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_generate.html @@ -0,0 +1,49 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for generating WebExtensions</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> + +function backgroundScript() { + browser.test.log("running background script"); + + browser.test.onMessage.addListener((x, y) => { + browser.test.assertEq(x, 10, "x is 10"); + browser.test.assertEq(y, 20, "y is 20"); + + browser.test.notifyPass("background test passed"); + }); + + browser.test.sendMessage("running", 1); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()" +}; + +add_task(function* test_background() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + info("load complete"); + yield extension.startup(); + let x = yield extension.awaitMessage("running"); + is(x, 1, "got correct value from extension"); + info("startup complete"); + extension.sendMessage(10, 20); + yield extension.awaitFinish(); + info("test complete"); + yield extension.unload(); + info("extension unloaded successfully"); +}); + +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_geturl.html @@ -0,0 +1,72 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> + +function backgroundScript() { + browser.runtime.onMessage.addListener(([url1, url2]) => { + var url3 = browser.runtime.getURL("test_file.html"); + var url4 = browser.extension.getURL("test_file.html"); + + browser.test.assertTrue(url1 !== undefined, "url1 defined"); + + browser.test.assertTrue(url1.startsWith("moz-extension://"), "url1 has correct scheme"); + browser.test.assertTrue(url1.endsWith("test_file.html"), "url1 has correct leaf name"); + + browser.test.assertEq(url1, url2, "url2 matches"); + browser.test.assertEq(url1, url3, "url3 matches"); + browser.test.assertEq(url1, url4, "url4 matches"); + + browser.test.notifyPass("geturl"); + }); +} + +function contentScript() { + var url1 = browser.runtime.getURL("test_file.html"); + var url2 = browser.extension.getURL("test_file.html"); + browser.runtime.sendMessage([url1, url2]); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + "content_scripts": [{ + "matches": ["http://mochi.test/*/file_sample.html"], + "js": ["content_script.js"], + "run_at": "document_start" + }] + }, + + files: { + "content_script.js": "(" + contentScript.toString() + ")()", + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + + let win = window.open("file_sample.html"); + + yield Promise.all([waitForLoad(win), extension.awaitFinish("geturl")]); + + win.close(); + + yield extension.unload(); + info("extension unloaded"); +}); + +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_connect.html @@ -0,0 +1,83 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +function backgroundScript() { + browser.runtime.onConnect.addListener(port => { + browser.test.assertEq(port.name, "ernie", "port name correct"); + browser.test.assertTrue(port.sender.url.endsWith("file_sample.html"), "URL correct"); + browser.test.assertTrue(port.sender.tab.url.endsWith("file_sample.html"), "tab URL correct"); + + var expected = "message 1"; + port.onMessage.addListener(msg => { + browser.test.assertEq(msg, expected, "message is expected"); + if (expected == "message 1") { + port.postMessage("message 2"); + expected = "message 3"; + } else if (expected == "message 3") { + expected = "disconnect"; + browser.test.notifyPass("runtime.connect"); + } + }); + port.onDisconnect.addListener(() => { + browser.test.assertEq(expected, "disconnect", "got disconnection at right time"); + }); + }); +} + +function contentScript() { + var port = browser.runtime.connect({name: "ernie"}); + port.postMessage("message 1"); + port.onMessage.addListener(msg => { + if (msg == "message 2") { + port.postMessage("message 3"); + port.disconnect(); + } + }); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + "permissions": ["tabs"], + "content_scripts": [{ + "matches": ["http://mochi.test/*/file_sample.html"], + "js": ["content_script.js"], + "run_at": "document_start" + }] + }, + + files: { + "content_script.js": "(" + contentScript.toString() + ")()", + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + + let win = window.open("file_sample.html"); + + yield Promise.all([waitForLoad(win), extension.awaitFinish("runtime.connect")]); + + win.close(); + + yield extension.unload(); + info("extension unloaded"); +}); +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_runtime_disconnect.html @@ -0,0 +1,75 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +function backgroundScript() { + browser.runtime.onConnect.addListener(port => { + browser.test.assertEq(port.name, "ernie", "port name correct"); + port.onDisconnect.addListener(() => { + browser.test.sendMessage("disconnected"); + }); + browser.test.sendMessage("connected"); + }); +} + +function contentScript() { + browser.runtime.connect({name: "ernie"}); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + "permissions": ["tabs"], + "content_scripts": [{ + "matches": ["http://mochi.test/*/file_sample.html"], + "js": ["content_script.js"], + "run_at": "document_start" + }] + }, + + files: { + "content_script.js": "(" + contentScript.toString() + ")()", + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + + let win = window.open("file_sample.html"); + yield Promise.all([waitForLoad(win), extension.awaitMessage("connected")]); + win.close(); + yield extension.awaitMessage("disconnected"); + + info("win.close() succeeded"); + + win = window.open("file_sample.html"); + yield Promise.all([waitForLoad(win), extension.awaitMessage("connected")]); + + // Add an "unload" listener so that we don't put the window in the + // bfcache. This way it gets destroyed immediately upon navigation. + win.addEventListener("unload", function() {}); + + win.location = "http://example.com"; + yield extension.awaitMessage("disconnected"); + win.close(); + + yield extension.unload(); + info("extension unloaded"); +}); +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sandbox_var.html @@ -0,0 +1,61 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for content script</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +function backgroundScript() { + browser.runtime.onMessage.addListener(result => { + browser.test.assertEq(result, 12, "x is 12"); + browser.test.notifyPass("background test passed"); + }); +} + +function contentScript() { + window.x = 12; + browser.runtime.onMessage.addListener(function() {}); + browser.runtime.sendMessage(window.x); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + "content_scripts": [{ + "matches": ["http://mochi.test/*/file_sample.html"], + "js": ["content_script.js"], + "run_at": "document_start" + }] + }, + + files: { + "content_script.js": "(" + contentScript.toString() + ")()", + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + + let win = window.open("file_sample.html"); + + yield Promise.all([waitForLoad(win), extension.awaitFinish()]); + + win.close(); + + yield extension.unload(); + info("extension unloaded"); +}); +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_doublereply.html @@ -0,0 +1,102 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +function backgroundScript() { + // Add two listeners that both send replies. We're supposed to ignore all but one + // of them. Which one is chosen is non-deterministic. + + browser.runtime.onMessage.addListener((msg, sender, sendReply) => { + browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct"); + + if (msg == "getreply") { + sendReply("reply1"); + } + }); + + browser.runtime.onMessage.addListener((msg, sender, sendReply) => { + browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct"); + + if (msg == "getreply") { + sendReply("reply2"); + } + }); + + function sleep(callback, n = 10) { + if (n == 0) { + callback(); + } else { + setTimeout(function() { sleep(callback, n - 1); }, 0); + } + } + + var done_count = 0; + browser.runtime.onMessage.addListener((msg, sender, sendReply) => { + browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct"); + + if (msg == "done") { + done_count++; + browser.test.assertEq(done_count, 1, "got exactly one reply"); + + // Go through the event loop a few times to make sure we don't get multiple replies. + sleep(function() { + browser.test.notifyPass("sendmessage_doublereply"); + }); + } + }); +} + +function contentScript() { + browser.runtime.sendMessage("getreply", function(resp) { + if (resp != "reply1" && resp != "reply2") { + return; // test failed + } + browser.runtime.sendMessage("done"); + }); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + "permissions": ["tabs"], + "content_scripts": [{ + "matches": ["http://mochi.test/*/file_sample.html"], + "js": ["content_script.js"], + "run_at": "document_start" + }] + }, + + files: { + "content_script.js": "(" + contentScript.toString() + ")()", + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + + let win = window.open("file_sample.html"); + + yield Promise.all([waitForLoad(win), extension.awaitFinish("sendmessage_doublereply")]); + + win.close(); + + yield extension.unload(); + info("extension unloaded"); +}); +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_sendmessage_reply.html @@ -0,0 +1,80 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +function backgroundScript() { + browser.runtime.onMessage.addListener((msg, sender, sendReply) => { + browser.test.assertTrue(sender.tab.url.endsWith("file_sample.html"), "sender url correct"); + + if (msg == 0) { + sendReply("reply1"); + } else if (msg == 1) { + window.setTimeout(function() { + sendReply("reply2"); + }, 0); + return true; + } else if (msg == 2) { + browser.test.notifyPass("sendmessage_reply"); + } + }); +} + +function contentScript() { + browser.runtime.sendMessage(0, function(resp1) { + if (resp1 != "reply1") { + return; // test failed + } + browser.runtime.sendMessage(1, function(resp2) { + if (resp2 != "reply2") { + return; // test failed + } + browser.runtime.sendMessage(2); + }); + }); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + "permissions": ["tabs"], + "content_scripts": [{ + "matches": ["http://mochi.test/*/file_sample.html"], + "js": ["content_script.js"], + "run_at": "document_start" + }] + }, + + files: { + "content_script.js": "(" + contentScript.toString() + ")()", + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + + let win = window.open("file_sample.html"); + + yield Promise.all([waitForLoad(win), extension.awaitFinish("sendmessage_reply")]); + + win.close(); + + yield extension.unload(); + info("extension unloaded"); +}); +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_simple.html @@ -0,0 +1,41 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for simple WebExtension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> + +add_task(function* test_simple() { + let extension = ExtensionTestUtils.loadExtension("simple"); + info("load complete"); + yield extension.startup(); + info("startup complete"); + yield extension.unload(); + info("extension unloaded successfully"); +}); + +add_task(function* test_background() { + let extension = ExtensionTestUtils.loadExtension("background"); + info("load complete"); + yield extension.startup(); + let x = yield extension.awaitMessage("running"); + is(x, 1, "got correct value from extension"); + info("startup complete"); + extension.sendMessage(10, 20); + yield extension.awaitFinish(); + info("test complete"); + yield extension.unload(); + info("extension unloaded successfully"); +}); + +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_storage.html @@ -0,0 +1,172 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>WebExtension test</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> + +function backgroundScript() { + function set(items) { + return new Promise(resolve => { + browser.storage.local.set(items, resolve); + }); + } + + function get(items) { + return new Promise(resolve => { + browser.storage.local.get(items, resolve); + }); + } + + function remove(items) { + return new Promise(resolve => { + browser.storage.local.remove(items, resolve); + }); + } + + function check(prop, value) { + return get(null).then(data => { + browser.test.assertEq(data[prop], value, "null getter worked for " + prop); + return get(prop); + }).then(data => { + browser.test.assertEq(data[prop], value, "string getter worked for " + prop); + return get([prop]); + }).then(data => { + browser.test.assertEq(data[prop], value, "array getter worked for " + prop); + return get({[prop]: undefined}); + }).then(data => { + browser.test.assertEq(data[prop], value, "object getter worked for " + prop); + }); + } + + var globalChanges = {}; + + browser.storage.onChanged.addListener((changes, storage) => { + browser.test.assertEq(storage, "local", "storage is local"); + Object.assign(globalChanges, changes); + }); + + function checkChanges(changes) { + function checkSub(obj1, obj2) { + for (var prop in obj1) { + browser.test.assertEq(obj1[prop].oldValue, obj2[prop].oldValue); + browser.test.assertEq(obj1[prop].newValue, obj2[prop].newValue); + } + } + + checkSub(changes, globalChanges); + checkSub(globalChanges, changes); + globalChanges = {}; + } + + // Set some data and then test getters. + set({"test-prop1": "value1", "test-prop2": "value2"}).then(() => { + checkChanges({"test-prop1": {newValue: "value1"}, "test-prop2": {newValue: "value2"}}); + return check("test-prop1", "value1"); + }).then(() => { + return check("test-prop2", "value2"); + }).then(() => { + return get({"test-prop1": undefined, "test-prop2": undefined, "other": "default"}); + }).then(data => { + browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct"); + browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct"); + browser.test.assertEq(data["other"], "default", "other correct"); + return get(["test-prop1", "test-prop2", "other"]); + }).then(data => { + browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct"); + browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct"); + browser.test.assertFalse("other" in data, "other correct"); + + // Remove data in various ways. + }).then(() => { + return remove("test-prop1"); + }).then(() => { + checkChanges({"test-prop1": {oldValue: "value1"}}); + return get(["test-prop1", "test-prop2"]); + }).then(data => { + browser.test.assertFalse("test-prop1" in data, "prop1 absent"); + browser.test.assertTrue("test-prop2" in data, "prop2 present"); + + return set({"test-prop1": "value1"}); + }).then(() => { + checkChanges({"test-prop1": {newValue: "value1"}}); + return get(["test-prop1", "test-prop2"]); + }).then(data => { + browser.test.assertEq(data["test-prop1"], "value1", "prop1 correct"); + browser.test.assertEq(data["test-prop2"], "value2", "prop2 correct"); + }).then(() => { + return remove(["test-prop1", "test-prop2"]); + }).then(() => { + checkChanges({"test-prop1": {oldValue: "value1"}, "test-prop2": {oldValue: "value2"}}); + return get(["test-prop1", "test-prop2"]); + }).then(data => { + browser.test.assertFalse("test-prop1" in data, "prop1 absent"); + browser.test.assertFalse("test-prop2" in data, "prop2 absent"); + + // Test cache invalidation. + }).then(() => { + return set({"test-prop1": "value1", "test-prop2": "value2"}); + }).then(() => { + globalChanges = {}; + browser.test.sendMessage("invalidate"); + return new Promise(resolve => browser.test.onMessage.addListener(resolve)); + }).then(() => { + return check("test-prop1", "value1"); + }).then(() => { + return check("test-prop2", "value2"); + + // Make sure we can store complex JSON data. + }).then(() => { + return set({"test-prop1": {str: "hello", bool: true, undef: undefined, obj: {}, arr: [1, 2]}}); + }).then(() => { + browser.test.assertEq(globalChanges["test-prop1"].oldValue, "value1", "oldValue correct"); + browser.test.assertEq(typeof(globalChanges["test-prop1"].newValue), "object", "newValue is obj"); + globalChanges = {}; + return get({"test-prop1": undefined}); + }).then(data => { + var obj = data["test-prop1"]; + + browser.test.assertEq(obj.str, "hello", "string part correct"); + browser.test.assertEq(obj.bool, true, "bool part correct"); + browser.test.assertEq(obj.undef, undefined, "undefined part correct"); + browser.test.assertEq(typeof(obj.obj), "object", "object part correct"); + browser.test.assertTrue(Array.isArray(obj.arr), "array part present"); + browser.test.assertEq(obj.arr[0], 1, "arr[0] part correct"); + browser.test.assertEq(obj.arr[1], 2, "arr[1] part correct"); + browser.test.assertEq(obj.arr.length, 2, "arr.length part correct"); + + }).then(() => { + browser.test.notifyPass("storage"); + }); +} + +let extensionData = { + background: "(" + backgroundScript.toString() + ")()", + manifest: { + permissions: ["storage"] + }, +}; + +add_task(function* test_contentscript() { + let extension = ExtensionTestUtils.loadExtension(extensionData); + yield extension.startup(); + info("extension loaded"); + yield extension.awaitMessage("invalidate"); + SpecialPowers.invalidateExtensionStorageCache(); + extension.sendMessage("invalidated"); + yield extension.awaitFinish("storage"); + yield extension.unload(); + info("extension unloaded"); +}); + +</script> + +</body> +</html>
new file mode 100644 --- /dev/null +++ b/toolkit/components/extensions/test/mochitest/test_ext_webrequest.html @@ -0,0 +1,126 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for simple WebExtension</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script> + <script type="text/javascript" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> +</head> +<body> + +<script type="application/javascript;version=1.8"> +"use strict"; + +const BASE = "http://mochi.test:8888/tests/toolkit/components/extensions/test/mochitest"; + +const expected_requested = [BASE + "/file_WebRequest_page1.html", + BASE + "/file_style_good.css", + BASE + "/file_style_bad.css", + BASE + "/file_style_redirect.css", + BASE + "/file_image_good.png", + BASE + "/file_image_bad.png", + BASE + "/file_image_redirect.png", + BASE + "/file_script_good.js", + BASE + "/file_script_bad.js", + BASE + "/file_script_redirect.js", + BASE + "/file_script_xhr.js", + BASE + "/file_WebRequest_page2.html", + BASE + "/nonexistent_script_url.js", + BASE + "/xhr_resource"]; + +const expected_sendHeaders = [BASE + "/file_WebRequest_page1.html", + BASE + "/file_style_good.css", + BASE + "/file_style_redirect.css", + BASE + "/file_image_good.png", + BASE + "/file_image_redirect.png", + BASE + "/file_script_good.js", + BASE + "/file_script_redirect.js", + BASE + "/file_script_xhr.js", + BASE + "/file_WebRequest_page2.html", + BASE + "/nonexistent_script_url.js", + BASE + "/xhr_resource"]; + +const expected_headersReceived = [BASE + "/file_WebRequest_page1.html", + BASE + "/file_style_good.css", + BASE + "/file_image_good.png", + BASE + "/file_script_good.js", + BASE + "/file_script_xhr.js", + BASE + "/file_WebRequest_page2.html", + BASE + "/nonexistent_script_url.js", + BASE + "/xhr_resource"]; + +function removeDupes(list) +{ + let j = 0; + for (let i = 1; i < list.length; i++) { + if (list[i] != list[j]) { + j++; + if (i != j) { + list[j] = list[i]; + } + } + } + list.length = j + 1; +} + +function compareLists(list1, list2, kind) +{ + list1.sort(); + removeDupes(list1); + list2.sort(); + removeDupes(list2); + is(String(list1), String(list2), `${kind} URLs correct`); +} + +function* test_once() +{ + let extension = ExtensionTestUtils.loadExtension("webrequest"); + yield extension.startup(); + yield extension.awaitMessage("ready"); + info("webrequest extension loaded"); + + yield new Promise(resolve => { setTimeout(resolve, 0); }); + + let win = window.open(); + + // Clear the image cache, since it gets in the way otherwise. + var imgTools = SpecialPowers.Cc["@mozilla.org/image/tools;1"].getService(SpecialPowers.Ci.imgITools); + var cache = imgTools.getImgCacheForDocument(win.document); + cache.clearCache(false); + + //yield waitForLoad(win); + info("about:blank loaded"); + + win.location = "file_WebRequest_page1.html"; + + yield waitForLoad(win); + info("test page loaded"); + + is(win.success, 2, "Good script ran"); + is(win.failure, undefined, "Failure script didn't run"); + + let style = win.getComputedStyle(win.document.getElementById("test"), null); + is(style.getPropertyValue("color"), "rgb(255, 0, 0)", "Good CSS loaded"); + + win.close(); + + extension.sendMessage("getResults"); + let [requested, sendHeaders, headersReceived] = yield extension.awaitMessage("results"); + + compareLists(requested, expected_requested, "requested"); + compareLists(sendHeaders, expected_sendHeaders, "sendHeaders"); + compareLists(headersReceived, expected_headersReceived, "headersReceived"); + + yield extension.unload(); + info("webrequest extension unloaded"); +} + +// Run the test twice to make sure it works with caching. +add_task(test_once); +add_task(test_once); +</script> + +</body>