From 88192f26e22978f114a42c0fdea73febb8abea29 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 4 Nov 2014 11:10:00 -0800 Subject: [PATCH 001/153] Bug 1085487 - Part 1: Center the reload button. r=lucasr --- .../base/resources/values-large-v11/styles.xml | 16 +++++++++++++--- mobile/android/base/resources/values/dimens.xml | 1 - 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/mobile/android/base/resources/values-large-v11/styles.xml b/mobile/android/base/resources/values-large-v11/styles.xml index 4ef0fddcdd0..12e142e9042 100644 --- a/mobile/android/base/resources/values-large-v11/styles.xml +++ b/mobile/android/base/resources/values-large-v11/styles.xml @@ -104,9 +104,19 @@ center @drawable/new_tablet_action_bar_button - - @dimen/new_tablet_browser_toolbar_menu_item_padding_vertical - @dimen/new_tablet_browser_toolbar_menu_item_padding_vertical + + 20dp + 21dp @dimen/new_tablet_browser_toolbar_menu_item_padding_horizontal @dimen/new_tablet_browser_toolbar_menu_item_padding_horizontal diff --git a/mobile/android/base/resources/values/dimens.xml b/mobile/android/base/resources/values/dimens.xml index 45daa8317af..e2ce1932917 100644 --- a/mobile/android/base/resources/values/dimens.xml +++ b/mobile/android/base/resources/values/dimens.xml @@ -36,7 +36,6 @@ 56dp 19dp - 21dp 5dp 3dp 5dp From 91c861fe5ca8fc929b271b3adb796a4c45820172 Mon Sep 17 00:00:00 2001 From: Michael Comella Date: Tue, 4 Nov 2014 11:10:11 -0800 Subject: [PATCH 002/153] Bug 1085487 - Part 2: Center tab count. r=lucasr --- .../newtablet/res/layout-large-v11/new_tablet_tabs_counter.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_tabs_counter.xml b/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_tabs_counter.xml index 9e9df2869be..33087d8e9ab 100644 --- a/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_tabs_counter.xml +++ b/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_tabs_counter.xml @@ -6,7 +6,7 @@ Date: Tue, 4 Nov 2014 11:51:06 -0800 Subject: [PATCH 003/153] Bug 1091287: Switch to the right browser before displaying a login prompt. r=mrbkap --- .../metro/components/LoginManagerPrompter.js | 2 +- dom/ipc/TabParent.cpp | 15 ++- .../components/LoginManagerPrompter.js | 2 +- .../passwordmgr/LoginManagerParent.jsm | 2 +- .../passwordmgr/nsILoginManagerPrompter.idl | 6 +- .../passwordmgr/nsLoginManagerPrompter.js | 21 ++-- .../passwordmgr/test/browser/authenticate.sjs | 110 ++++++++++++++++++ .../passwordmgr/test/browser/browser.ini | 3 + .../browser/browser_passwordmgr_switchtab.js | 42 +++++++ 9 files changed, 188 insertions(+), 15 deletions(-) create mode 100644 toolkit/components/passwordmgr/test/browser/authenticate.sjs create mode 100644 toolkit/components/passwordmgr/test/browser/browser_passwordmgr_switchtab.js diff --git a/browser/metro/components/LoginManagerPrompter.js b/browser/metro/components/LoginManagerPrompter.js index f21538714fe..818b4d48123 100644 --- a/browser/metro/components/LoginManagerPrompter.js +++ b/browser/metro/components/LoginManagerPrompter.js @@ -123,7 +123,7 @@ LoginManagerPrompter.prototype = { }, - setE10sData : function (aBrowser) { + setE10sData : function (aBrowser, aOpener) { // XXX Implement me! throw new Error("Not Yet Implemented"); }, diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp index b0234c8d966..d52eb7b40e0 100644 --- a/dom/ipc/TabParent.cpp +++ b/dom/ipc/TabParent.cpp @@ -71,6 +71,7 @@ #include "nsAuthInformationHolder.h" #include "nsICancelable.h" #include "gfxPrefs.h" +#include "nsILoginManagerPrompter.h" #include using namespace mozilla::dom; @@ -1880,8 +1881,18 @@ TabParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid, // Get an auth prompter for our window so that the parenting // of the dialogs works as it should when using tabs. - return wwatch->GetPrompt(window, iid, - reinterpret_cast(aResult)); + nsCOMPtr prompt; + rv = wwatch->GetPrompt(window, iid, getter_AddRefs(prompt)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr prompter = do_QueryInterface(prompt); + if (prompter) { + nsCOMPtr browser = do_QueryInterface(mFrameElement); + prompter->SetE10sData(browser, nullptr); + } + + *aResult = prompt.forget().take(); + return NS_OK; } PColorPickerParent* diff --git a/mobile/android/components/LoginManagerPrompter.js b/mobile/android/components/LoginManagerPrompter.js index b49c8bbab1b..33dbc49e8e8 100644 --- a/mobile/android/components/LoginManagerPrompter.js +++ b/mobile/android/components/LoginManagerPrompter.js @@ -108,7 +108,7 @@ LoginManagerPrompter.prototype = { this.log("===== initialized ====="); }, - setE10sData : function (aBrowser) { + setE10sData : function (aBrowser, aOpener) { throw new Error("This should be filled in when Android is multiprocess"); }, diff --git a/toolkit/components/passwordmgr/LoginManagerParent.jsm b/toolkit/components/passwordmgr/LoginManagerParent.jsm index 093a2be7490..4675ff95286 100644 --- a/toolkit/components/passwordmgr/LoginManagerParent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm @@ -216,7 +216,7 @@ var LoginManagerParent = { target.ownerDocument.defaultView : target.contentWindow); if (target.isRemoteBrowser) - prompterSvc.setE10sData({ browser: target, opener: opener }); + prompterSvc.setE10sData(target, opener); return prompterSvc; } diff --git a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl index 261791fec59..9ddbe787637 100644 --- a/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl +++ b/toolkit/components/passwordmgr/nsILoginManagerPrompter.idl @@ -6,9 +6,10 @@ #include "nsISupports.idl" interface nsILoginInfo; +interface nsIDOMElement; interface nsIDOMWindow; -[scriptable, uuid(88b75787-a78c-43aa-bfe8-52c3248b8dfd)] +[scriptable, uuid(425f73b9-b2db-4e8a-88c5-9ac2512934ce)] interface nsILoginManagerPrompter : nsISupports { /** * Initialize the prompter. Must be called before using other interfaces. @@ -27,8 +28,9 @@ interface nsILoginManagerPrompter : nsISupports { * window passed to init. * * @param aBrowser the to use for this prompter. + * @param aOpener the opener to use for this prompter. */ - void setE10sData(in jsval aData); + void setE10sData(in nsIDOMElement aBrowser, in nsIDOMWindow aOpener); /** * Ask the user if they want to save a login (Yes, Never, Not Now) diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index daf45609723..5733b86617f 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -11,6 +11,7 @@ const Cr = Components.results; Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); +Components.utils.import("resource://gre/modules/SharedPromptUtils.jsm"); /* * LoginManagerPromptFactory @@ -577,10 +578,14 @@ LoginManagerPrompter.prototype = { "Epic fail in promptAuth: " + e + "\n"); } - var ok = canAutologin || - this._promptService.promptAuth(this._window, - aChannel, aLevel, aAuthInfo, - checkboxLabel, checkbox); + var ok = canAutologin; + if (!ok) { + if (this._window) + PromptUtils.fireDialogEvent(this._window, "DOMWillOpenModalDialog", this._browser); + ok = this._promptService.promptAuth(this._window, + aChannel, aLevel, aAuthInfo, + checkboxLabel, checkbox); + } // If there's a notification box, use it to allow the user to // determine if the login should be saved. If there isn't a @@ -713,11 +718,11 @@ LoginManagerPrompter.prototype = { this.log("===== initialized ====="); }, - setE10sData : function (aData) { + setE10sData : function (aBrowser, aOpener) { if (!(this._window instanceof Ci.nsIDOMChromeWindow)) throw new Error("Unexpected call"); - this._browser = aData.browser; - this._opener = aData.opener; + this._browser = aBrowser; + this._opener = aOpener; }, @@ -1224,7 +1229,7 @@ LoginManagerPrompter.prototype = { } let browser; - if (useOpener && isE10s) { + if (useOpener && this._opener && isE10s) { // In e10s, we have to reconstruct the opener browser from // the CPOW passed in the message (and then passed to us in // setE10sData). diff --git a/toolkit/components/passwordmgr/test/browser/authenticate.sjs b/toolkit/components/passwordmgr/test/browser/authenticate.sjs new file mode 100644 index 00000000000..7e4cf3c0252 --- /dev/null +++ b/toolkit/components/passwordmgr/test/browser/authenticate.sjs @@ -0,0 +1,110 @@ +function handleRequest(request, response) +{ + var match; + var requestAuth = true; + + // Allow the caller to drive how authentication is processed via the query. + // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar + // The extra ? allows the user/pass/realm checks to succeed if the name is + // at the beginning of the query string. + var query = "?" + request.queryString; + + var expected_user = "test", expected_pass = "testpass", realm = "mochitest"; + + // Look for an authentication header, if any, in the request. + // + // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== + // + // This test only supports Basic auth. The value sent by the client is + // "username:password", obscured with base64 encoding. + + var actual_user = "", actual_pass = "", authHeader, authPresent = false; + if (request.hasHeader("Authorization")) { + authPresent = true; + authHeader = request.getHeader("Authorization"); + match = /Basic (.+)/.exec(authHeader); + if (match.length != 2) + throw "Couldn't parse auth header: " + authHeader; + + var userpass = base64ToString(match[1]); // no atob() :-( + match = /(.*):(.*)/.exec(userpass); + if (match.length != 3) + throw "Couldn't decode auth header: " + userpass; + actual_user = match[1]; + actual_pass = match[2]; + } + + // Don't request authentication if the credentials we got were what we + // expected. + if (expected_user == actual_user && + expected_pass == actual_pass) { + requestAuth = false; + } + + if (requestAuth) { + response.setStatusLine("1.0", 401, "Authentication required"); + response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true); + } else { + response.setStatusLine("1.0", 200, "OK"); + } + + response.setHeader("Content-Type", "application/xhtml+xml", false); + response.write(""); + response.write("

Login: " + (requestAuth ? "FAIL" : "PASS") + "

\n"); + response.write("

Auth: " + authHeader + "

\n"); + response.write("

User: " + actual_user + "

\n"); + response.write("

Pass: " + actual_pass + "

\n"); + response.write(""); +} + + +// base64 decoder +// +// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa() +// doesn't seem to exist. :-( +/* Convert Base64 data to a string */ +const toBinaryTable = [ + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, + -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63, + 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1, + -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1 +]; +const base64Pad = '='; + +function base64ToString(data) { + + var result = ''; + var leftbits = 0; // number of bits decoded, but yet to be appended + var leftdata = 0; // bits decoded, but yet to be appended + + // Convert one by one. + for (var i = 0; i < data.length; i++) { + var c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + var padding = (data[i] == base64Pad); + // Skip illegal characters and whitespace + if (c == -1) continue; + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) + result += String.fromCharCode((leftdata >> leftbits) & 0xff); + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) + throw Components.Exception('Corrupted base64 string'); + + return result; +} diff --git a/toolkit/components/passwordmgr/test/browser/browser.ini b/toolkit/components/passwordmgr/test/browser/browser.ini index e4c39d27320..430fc39d386 100644 --- a/toolkit/components/passwordmgr/test/browser/browser.ini +++ b/toolkit/components/passwordmgr/test/browser/browser.ini @@ -1,7 +1,10 @@ [DEFAULT] +support-files = + authenticate.sjs [browser_passwordmgr_fields.js] [browser_passwordmgr_observers.js] [browser_passwordmgr_sort.js] +[browser_passwordmgr_switchtab.js] [browser_passwordmgrcopypwd.js] [browser_passwordmgrdlg.js] diff --git a/toolkit/components/passwordmgr/test/browser/browser_passwordmgr_switchtab.js b/toolkit/components/passwordmgr/test/browser/browser_passwordmgr_switchtab.js new file mode 100644 index 00000000000..72efde5d6ac --- /dev/null +++ b/toolkit/components/passwordmgr/test/browser/browser_passwordmgr_switchtab.js @@ -0,0 +1,42 @@ +/* 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/. */ + +const PROMPT_URL = "chrome://global/content/commonDialog.xul"; +const { interfaces: Ci } = Components; + +function test() { + waitForExplicitFinish(); + + let tab = gBrowser.addTab(); + isnot(tab, gBrowser.selectedTab, "New tab shouldn't be selected"); + + let listener = { + onOpenWindow: function(window) { + var domwindow = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIDOMWindow); + waitForFocus(() => { + is(domwindow.document.location.href, PROMPT_URL, "Should have seen a prompt window"); + is(domwindow.args.promptType, "promptUserAndPass", "Should be an authenticate prompt"); + + is(gBrowser.selectedTab, tab, "Should have selected the new tab"); + + domwindow.document.documentElement.cancelDialog() + }, domwindow); + }, + + onCloseWindow: function() { + } + } + + Services.wm.addListener(listener); + registerCleanupFunction(() => { + Services.wm.removeListener(listener); + gBrowser.removeTab(tab); + }) + + tab.linkedBrowser.addEventListener("load", () => { + finish(); + }, true); + tab.linkedBrowser.loadURI("http://example.com/browser/toolkit/components/passwordmgr/test/browser/authenticate.sjs"); +} From 0694c8dcd906d13e661a9951ac39cdce319ff4b2 Mon Sep 17 00:00:00 2001 From: Dave Townsend Date: Tue, 4 Nov 2014 12:05:08 -0800 Subject: [PATCH 004/153] Bug 1093221: RemoteWebNavigation.loadURI should accept and pass referrer argument. r=mconley --- toolkit/content/browser-child.js | 8 +- toolkit/modules/RemoteWebNavigation.jsm | 13 +- toolkit/modules/tests/browser/browser.ini | 3 + .../browser/browser_RemoteWebNavigation.js | 150 ++++++++++++++++++ toolkit/modules/tests/browser/dummy_page.html | 7 + 5 files changed, 174 insertions(+), 7 deletions(-) create mode 100644 toolkit/modules/tests/browser/browser_RemoteWebNavigation.js create mode 100644 toolkit/modules/tests/browser/dummy_page.html diff --git a/toolkit/content/browser-child.js b/toolkit/content/browser-child.js index ac62758a472..38aee8e295c 100644 --- a/toolkit/content/browser-child.js +++ b/toolkit/content/browser-child.js @@ -166,7 +166,7 @@ let WebNavigation = { this.gotoIndex(message.data.index); break; case "WebNavigation:LoadURI": - this.loadURI(message.data.uri, message.data.flags); + this.loadURI(message.data.uri, message.data.flags, message.data.referrer); break; case "WebNavigation:Reload": this.reload(message.data.flags); @@ -192,12 +192,14 @@ let WebNavigation = { this._webNavigation.gotoIndex(index); }, - loadURI: function(uri, flags) { + loadURI: function(uri, flags, referrer) { #ifdef MOZ_CRASHREPORTER if (CrashReporter.enabled) CrashReporter.annotateCrashReport("URL", uri); #endif - this._webNavigation.loadURI(uri, flags, null, null, null); + if (referrer) + referrer = Services.io.newURI(referrer, null, null); + this._webNavigation.loadURI(uri, flags, referrer, null, null); }, reload: function(flags) { diff --git a/toolkit/modules/RemoteWebNavigation.jsm b/toolkit/modules/RemoteWebNavigation.jsm index 04181e202b3..6e5a9339c37 100644 --- a/toolkit/modules/RemoteWebNavigation.jsm +++ b/toolkit/modules/RemoteWebNavigation.jsm @@ -5,9 +5,7 @@ this.EXPORTED_SYMBOLS = ["RemoteWebNavigation"]; -const Ci = Components.interfaces; -const Cc = Components.classes; -const Cu = Components.utils; +const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); @@ -60,8 +58,15 @@ RemoteWebNavigation.prototype = { this._sendMessage("WebNavigation:GotoIndex", {index: aIndex}); }, loadURI: function(aURI, aLoadFlags, aReferrer, aPostData, aHeaders) { + if (aPostData || aHeaders) + throw Components.Exception("RemoteWebNavigation doesn't accept postdata or headers.", Cr.NS_ERROR_INVALID_ARGS); + this._browser._contentTitle = ""; - this._sendMessage("WebNavigation:LoadURI", {uri: aURI, flags: aLoadFlags}); + this._sendMessage("WebNavigation:LoadURI", { + uri: aURI, + flags: aLoadFlags, + referrer: aReferrer ? aReferrer.spec : null + }); }, reload: function(aReloadFlags) { this._sendMessage("WebNavigation:Reload", {flags: aReloadFlags}); diff --git a/toolkit/modules/tests/browser/browser.ini b/toolkit/modules/tests/browser/browser.ini index 8341d51d499..49893eac5e3 100644 --- a/toolkit/modules/tests/browser/browser.ini +++ b/toolkit/modules/tests/browser/browser.ini @@ -1,4 +1,6 @@ [DEFAULT] +support-files = + dummy_page.html [browser_Battery.js] [browser_Deprecated.js] @@ -6,4 +8,5 @@ skip-if = e10s # Bug ?????? - test already uses content scripts, but still fails only under e10s. [browser_Geometry.js] [browser_InlineSpellChecker.js] +[browser_RemoteWebNavigation.js] [browser_Troubleshoot.js] diff --git a/toolkit/modules/tests/browser/browser_RemoteWebNavigation.js b/toolkit/modules/tests/browser/browser_RemoteWebNavigation.js new file mode 100644 index 00000000000..0ce612ebd29 --- /dev/null +++ b/toolkit/modules/tests/browser/browser_RemoteWebNavigation.js @@ -0,0 +1,150 @@ +const { interfaces: Ci, classes: Cc, utils: Cu, results: Cr } = Components; + +const DUMMY1 = "http://example.com/browser/toolkit/modules/tests/browser/dummy_page.html"; +const DUMMY2 = "http://example.org/browser/toolkit/modules/tests/browser/dummy_page.html" + +function waitForLoad(browser = gBrowser.selectedBrowser) { + return new Promise(resolve => { + browser.addEventListener("load", function listener() { + browser.removeEventListener("load", listener, true); + resolve(); + }, true); + }); +} + +function waitForPageShow(browser = gBrowser.selectedBrowser) { + return new Promise(resolve => { + browser.addEventListener("pageshow", function listener() { + browser.removeEventListener("pageshow", listener, true); + resolve(); + }, true); + }); +} + +function makeURI(url) { + return Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService). + newURI(url, null, null); +} + +// Tests that loadURI accepts a referrer and it is included in the load. +add_task(function* test_referrer() { + gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + + browser.webNavigation.loadURI(DUMMY1, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + makeURI(DUMMY2), + null, null); + yield waitForLoad(); + is(browser.contentWindow.location, DUMMY1, "Should have loaded the right URL"); + is(browser.contentDocument.referrer, DUMMY2, "Should have the right referrer"); + + gBrowser.removeCurrentTab(); +}); + +// Tests that remote access to webnavigation.sessionHistory works. +add_task(function* test_history() { + gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + + browser.webNavigation.loadURI(DUMMY1, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + null, null, null); + yield waitForLoad(); + + browser.webNavigation.loadURI(DUMMY2, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + null, null, null); + yield waitForLoad(); + + let history = browser.webNavigation.sessionHistory; + is(history.count, 2, "Should be two history items"); + is(history.index, 1, "Should be at the right place in history"); + let entry = history.getEntryAtIndex(0, false); + is(entry.URI.spec, DUMMY1, "Should have the right history entry"); + entry = history.getEntryAtIndex(1, false); + is(entry.URI.spec, DUMMY2, "Should have the right history entry"); + + let promise = waitForPageShow(); + browser.webNavigation.goBack(); + yield promise; + is(history.index, 0, "Should be at the right place in history"); + + promise = waitForPageShow(); + browser.webNavigation.goForward(); + yield promise; + is(history.index, 1, "Should be at the right place in history"); + + promise = waitForPageShow(); + browser.webNavigation.gotoIndex(0); + yield promise; + is(history.index, 0, "Should be at the right place in history"); + + gBrowser.removeCurrentTab(); +}); + +// Tests that load flags are passed through to the content process. +add_task(function* test_flags() { + gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + + browser.webNavigation.loadURI(DUMMY1, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + null, null, null); + yield waitForLoad(); + + browser.webNavigation.loadURI(DUMMY2, + Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY, + null, null, null); + yield waitForLoad(); + + let history = browser.webNavigation.sessionHistory; + is(history.count, 1, "Should be one history item"); + is(history.index, 0, "Should be at the right place in history"); + let entry = history.getEntryAtIndex(0, false); + is(entry.URI.spec, DUMMY2, "Should have the right history entry"); + + browser.webNavigation.loadURI(DUMMY1, + Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_HISTORY, + null, null, null); + yield waitForLoad(); + + is(history.count, 1, "Should still be one history item"); + is(history.index, 0, "Should be at the right place in history"); + entry = history.getEntryAtIndex(0, false); + is(entry.URI.spec, DUMMY2, "Should have the right history entry"); + + gBrowser.removeCurrentTab(); +}); + +// Tests that attempts to use unsupported arguments throw an exception. +add_task(function* test_badarguments() { + if (!gMultiProcessBrowser) + return; + + gBrowser.selectedTab = gBrowser.addTab(); + let browser = gBrowser.selectedBrowser; + + try { + browser.webNavigation.loadURI(DUMMY1, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + null, {}, null); + ok(false, "Should have seen an exception from trying to pass some postdata"); + } + catch (e) { + ok(true, "Should have seen an exception from trying to pass some postdata"); + } + + try { + browser.webNavigation.loadURI(DUMMY1, + Ci.nsIWebNavigation.LOAD_FLAGS_NONE, + null, null, {}); + ok(false, "Should have seen an exception from trying to pass some headers"); + } + catch (e) { + ok(true, "Should have seen an exception from trying to pass some headers"); + } + + gBrowser.removeCurrentTab(); +}); diff --git a/toolkit/modules/tests/browser/dummy_page.html b/toolkit/modules/tests/browser/dummy_page.html new file mode 100644 index 00000000000..c1c9a4e0434 --- /dev/null +++ b/toolkit/modules/tests/browser/dummy_page.html @@ -0,0 +1,7 @@ + + + + +

Page

+ + From e168ce3f3df27451c7d465f17f45c2c795dc5e69 Mon Sep 17 00:00:00 2001 From: Mike Conley Date: Tue, 4 Nov 2014 12:10:15 -0500 Subject: [PATCH 005/153] Bug 1089815 - View source print preview browser does not have a message manager. r=Mossop. 's do not get messageManager's constructed for them unless they have their type attribute set to "content", "content-targetable", or "content-primary". This patch updates the view source print preview browser with that type attribute, and also updates the PrintUtils documentation to mention this requirement. --HG-- extra : amend_source : 12638e8fd0c0da9b3036d7892d02a360b7513ec5 --- toolkit/components/printing/content/printUtils.js | 15 ++++++++++++--- .../components/viewsource/content/viewSource.js | 2 ++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/toolkit/components/printing/content/printUtils.js b/toolkit/components/printing/content/printUtils.js index 6f7ead241ba..65d5fafd186 100644 --- a/toolkit/components/printing/content/printUtils.js +++ b/toolkit/components/printing/content/printUtils.js @@ -19,6 +19,10 @@ * the object that listens and responds to the messages that PrintUtils * sends. * + * This also means that 's that hope to use PrintUtils must have + * their type attribute set to either "content", "content-targetable", or + * "content-primary". + * * PrintUtils sends messages at different points in its implementation, but * their documentation is consolidated here for ease-of-access. * @@ -122,7 +126,9 @@ var PrintUtils = { * The remote that contains aWindow. This argument is * not necessary if aWindow came from a non-remote browser, but is * strictly required otherwise. This function will throw if aWindow - * comes from a remote browser and aBrowser is not provided. + * comes from a remote browser and aBrowser is not provided. This + * browser must have its type attribute set to "content", + * "content-targetable", or "content-primary". */ print: function (aWindow, aBrowser) { @@ -176,11 +182,14 @@ var PrintUtils = { * An object that defines the following functions: * * getPrintPreviewBrowser: - * Returns the to display the print preview in. + * Returns the to display the print preview in. This + * must have its type attribute set to "content", + * "content-targetable", or "content-primary". * * getSourceBrowser: * Returns the that contains the document being - * printed. + * printed. This must have its type attribute set to + * "content", "content-targetable", or "content-primary". * * getNavToolbox: * Returns the primary toolbox for this window. diff --git a/toolkit/components/viewsource/content/viewSource.js b/toolkit/components/viewsource/content/viewSource.js index a6dea499b34..2adbf442c68 100644 --- a/toolkit/components/viewsource/content/viewSource.js +++ b/toolkit/components/viewsource/content/viewSource.js @@ -335,9 +335,11 @@ var PrintPreviewListener = { browser = document.createElement("browser"); browser.setAttribute("id", "ppBrowser"); browser.setAttribute("flex", "1"); + browser.setAttribute("type", "content"); document.getElementById("appcontent"). insertBefore(browser, document.getElementById("FindToolbar")); } + return browser; }, getSourceBrowser: function () { From d61f2761ec05cd56f07d61dab62cfa1dcbd58f54 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Tue, 4 Nov 2014 12:42:58 -0800 Subject: [PATCH 006/153] Bug 1019726 - Part 2: exclude Maithili from locale switcher on Android < 21. r=margaret --- .../base/preferences/LocaleListPreference.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mobile/android/base/preferences/LocaleListPreference.java b/mobile/android/base/preferences/LocaleListPreference.java index 206fecbbefe..5259354821f 100644 --- a/mobile/android/base/preferences/LocaleListPreference.java +++ b/mobile/android/base/preferences/LocaleListPreference.java @@ -172,6 +172,12 @@ public class LocaleListPreference extends ListPreference { * on this device without known issues. */ public boolean isUsable(CharacterValidator validator) { + if (Versions.preLollipop && this.tag.matches("[a-zA-Z]{3}.*")) { + // Earlier versions of Android can't load three-char locale code + // resources. + return false; + } + // Oh, for Java 7 switch statements. if (this.tag.equals("bn-IN")) { // Bengali sometimes has an English label if the Bengali script @@ -295,8 +301,11 @@ public class LocaleListPreference extends ListPreference { values[0] = ""; for (int i = 0; i < count; ++i) { - entries[i + 1] = descriptors[i].getDisplayName(); - values[i + 1] = descriptors[i].getTag(); + final String displayName = descriptors[i].getDisplayName(); + final String tag = descriptors[i].getTag(); + Log.v(LOG_TAG, displayName + " => " + tag); + entries[i + 1] = displayName; + values[i + 1] = tag; } setEntries(entries); From 3b48e0e71a394efe9659315caa11f2636732654f Mon Sep 17 00:00:00 2001 From: Stephen Gowan Date: Tue, 4 Nov 2014 12:42:59 -0800 Subject: [PATCH 007/153] Bug 685944 - Warn if response body doesn't match Content-Length header. r=rnewman --- services/sync/modules/resource.js | 8 ++ services/sync/modules/rest.js | 16 ++++ .../unit/test_warn_on_truncated_response.js | 95 +++++++++++++++++++ services/sync/tests/unit/xpcshell.ini | 2 + 4 files changed, 121 insertions(+) create mode 100644 services/sync/tests/unit/test_warn_on_truncated_response.js diff --git a/services/sync/modules/resource.js b/services/sync/modules/resource.js index d2a7f728738..d53353eaff9 100644 --- a/services/sync/modules/resource.js +++ b/services/sync/modules/resource.js @@ -302,6 +302,14 @@ AsyncResource.prototype = { Observers.notify("weave:service:quota:remaining", parseInt(headers["x-weave-quota-remaining"], 10)); } + + let contentLength = headers["content-length"]; + if (success && contentLength && data && + contentLength != data.length) { + this._log.warn("The response body's length of: " + data.length + + " doesn't match the header's content-length of: " + + contentLength + "."); + } } catch (ex) { this._log.debug("Caught exception " + CommonUtils.exceptionStr(ex) + " visiting headers in _onComplete."); diff --git a/services/sync/modules/rest.js b/services/sync/modules/rest.js index bb5757a21b5..34382eed5fa 100644 --- a/services/sync/modules/rest.js +++ b/services/sync/modules/rest.js @@ -86,5 +86,21 @@ SyncStorageRequest.prototype = { Svc.Obs.notify("weave:service:quota:remaining", parseInt(headers["x-weave-quota-remaining"], 10)); } + }, + + onStopRequest: function onStopRequest(channel, context, statusCode) { + if (this.status != this.ABORTED) { + let resp = this.response; + let contentLength = resp.headers ? resp.headers["content-length"] : ""; + + if (resp.success && contentLength && + contentLength != resp.body.length) { + this._log.warn("The response body's length of: " + resp.body.length + + " doesn't match the header's content-length of: " + + contentLength + "."); + } + } + + RESTRequest.prototype.onStopRequest.apply(this, arguments); } }; diff --git a/services/sync/tests/unit/test_warn_on_truncated_response.js b/services/sync/tests/unit/test_warn_on_truncated_response.js new file mode 100644 index 00000000000..a9f070ee418 --- /dev/null +++ b/services/sync/tests/unit/test_warn_on_truncated_response.js @@ -0,0 +1,95 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Cu.import("resource://testing-common/httpd.js"); +Cu.import("resource://services-sync/resource.js"); +Cu.import("resource://services-sync/rest.js"); + +function run_test() { + initTestLogging("Trace"); + run_next_test(); +} + +let BODY = "response body"; +// contentLength needs to be longer than the response body +// length in order to get a mismatch between what is sent in +// the response and the content-length header value. +let contentLength = BODY.length + 1; + +function contentHandler(request, response) { + _("Handling request."); + response.setHeader("Content-Type", "text/plain"); + response.setStatusLine(request.httpVersion, 200, "OK"); + response.bodyOutputStream.write(BODY, contentLength); +} + +function getWarningMessages(log) { + let warnMessages = []; + let warn = log.warn; + log.warn = function (message) { + let regEx = /The response body\'s length of: \d+ doesn\'t match the header\'s content-length of: \d+/i + if (message.match(regEx)) { + warnMessages.push(message); + } + warn.call(log, message); + } + return warnMessages; +} + +add_test(function test_resource_logs_content_length_mismatch() { + _("Issuing request."); + let httpServer = httpd_setup({"/content": contentHandler}); + let resource = new Resource(httpServer.baseURI + "/content"); + + let warnMessages = getWarningMessages(resource._log); + let result = resource.get(); + + notEqual(warnMessages.length, 0, "test that a warning was logged"); + notEqual(result.length, contentLength); + equal(result, BODY); + + httpServer.stop(run_next_test); +}); + +add_test(function test_async_resource_logs_content_length_mismatch() { + _("Issuing request."); + let httpServer = httpd_setup({"/content": contentHandler}); + let asyncResource = new AsyncResource(httpServer.baseURI + "/content"); + + let warnMessages = getWarningMessages(asyncResource._log); + + asyncResource.get(function (error, content) { + equal(error, null); + equal(content, BODY); + notEqual(warnMessages.length, 0, "test that warning was logged"); + notEqual(content.length, contentLength); + httpServer.stop(run_next_test); + }); +}); + +add_test(function test_sync_storage_request_logs_content_length_mismatch() { + _("Issuing request."); + let httpServer = httpd_setup({"/content": contentHandler}); + let request = new SyncStorageRequest(httpServer.baseURI + "/content"); + let warnMessages = getWarningMessages(request._log); + + // Setting this affects how received data is read from the underlying + // nsIHttpChannel in rest.js. If it's left as UTF-8 (the default) an + // nsIConverterInputStream is used and the data read from channel's stream + // isn't truncated at the null byte mark (\u0000). Therefore the + // content-length mismatch being tested for doesn't occur. Setting it to + // a falsy value results in an nsIScriptableInputStream being used to read + // the stream, which stops reading at the null byte mark resulting in a + // content-length mismatch. + request.charset = ""; + + request.get(function (error) { + equal(error, null); + equal(this.response.body, BODY); + notEqual(warnMessages.length, 0, "test that a warning was logged"); + notEqual(BODY.length, contentLength); + httpServer.stop(run_next_test); + }); +}); diff --git a/services/sync/tests/unit/xpcshell.ini b/services/sync/tests/unit/xpcshell.ini index 89bcb9cf371..ab750423b67 100644 --- a/services/sync/tests/unit/xpcshell.ini +++ b/services/sync/tests/unit/xpcshell.ini @@ -170,3 +170,5 @@ skip-if = debug [test_healthreport.js] skip-if = ! healthreport + +[test_warn_on_truncated_response.js] From 7ccee6d40bcae806a06a6f8239f3198a2d5915a0 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Tue, 4 Nov 2014 13:23:16 -0800 Subject: [PATCH 008/153] Bug 1093708 - Include mdpi assets in constrained builds. r=nalexander --- mobile/android/base/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/android/base/Makefile.in b/mobile/android/base/Makefile.in index 32555de9d0b..e168594f927 100644 --- a/mobile/android/base/Makefile.in +++ b/mobile/android/base/Makefile.in @@ -372,7 +372,7 @@ $(1): $$(call mkdir_deps,$(filter-out ./,$(dir $(3) $(4) $(5)))) $(2) -F $(3) \ -J $(4) \ --output-text-symbols $(5) \ - $(if $(MOZ_ANDROID_RESOURCE_CONSTRAINED),-c hdpi,) \ + $(if $(MOZ_ANDROID_RESOURCE_CONSTRAINED),-c mdpi,hdpi,) \ --ignore-assets "$$(ANDROID_AAPT_IGNORE)" endef From 2683d1a61ac1a6a2bec5caba27a8d8f35a229376 Mon Sep 17 00:00:00 2001 From: Brian Grinstead Date: Tue, 4 Nov 2014 13:31:44 -0800 Subject: [PATCH 009/153] Bug 1093766 - Change background color for about dialog on aurora;r=jaws --- browser/branding/aurora/content/aboutDialog.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/branding/aurora/content/aboutDialog.css b/browser/branding/aurora/content/aboutDialog.css index 6ac51e3d34b..209c53244eb 100644 --- a/browser/branding/aurora/content/aboutDialog.css +++ b/browser/branding/aurora/content/aboutDialog.css @@ -5,7 +5,7 @@ #aboutDialogContainer { background-image: url("chrome://branding/content/about-background.png"); background-repeat: no-repeat; - background-color: rgb(19,8,36); + background-color: rgb(26,58,99); color: #fff; } From c1da8b281375943276d921ba952e72db9854b158 Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Tue, 4 Nov 2014 13:34:45 -0800 Subject: [PATCH 010/153] Bug 1007409 - Cache reading list articles in files, not indexedDB. r=rnewman --- mobile/android/chrome/content/Reader.js | 308 +++++++++++------------ mobile/android/chrome/content/browser.js | 2 +- 2 files changed, 146 insertions(+), 164 deletions(-) diff --git a/mobile/android/chrome/content/Reader.js b/mobile/android/chrome/content/Reader.js index fbf60365d62..f8632e5643d 100644 --- a/mobile/android/chrome/content/Reader.js +++ b/mobile/android/chrome/content/Reader.js @@ -4,9 +4,12 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils", + "resource://services-common/utils.js"); + let Reader = { - // Version of the cache database schema - DB_VERSION: 1, + // Version of the cache schema. + CACHE_VERSION: 1, DEBUG: 0, @@ -76,7 +79,8 @@ let Reader = { observe: function(aMessage, aTopic, aData) { switch(aTopic) { case "Reader:Removed": { - this.removeArticleFromCache(aData); + let uri = Services.io.newURI(aData, null, null); + this.removeArticleFromCache(uri).catch(e => Cu.reportError("Error removing article from cache: " + e)); break; } @@ -91,10 +95,13 @@ let Reader = { _addTabToReadingList: function(tabID) { let tab = BrowserApp.getTabForId(tabID); - let currentURI = tab.browser.currentURI; - let urlWithoutRef = currentURI.specIgnoringRef; + if (!tab) { + Cu.reportError("Can't add tab to reading list because no tab found for ID: " + tabID); + return; + } + let uri = tab.browser.currentURI; - this.getArticleFromCache(urlWithoutRef, (article) => { + this.getArticleFromCache(uri).then(article => { // If the article is already in the cache, just use that. if (article) { this.addArticleToReadingList(article); @@ -102,7 +109,7 @@ let Reader = { } // Otherwise, get the article data from the tab. - this.getArticleForTab(tabID, urlWithoutRef, (article) => { + this.getArticleForTab(tabID, uri.specIgnoringRef, article => { if (article) { this.addArticleToReadingList(article); } else { @@ -114,7 +121,7 @@ let Reader = { }); } }); - }); + }, e => Cu.reportError("Error trying to get article from cache: " + e)); }, addArticleToReadingList: function(article) { @@ -131,7 +138,7 @@ let Reader = { excerpt: article.excerpt || "", }); - this.storeArticleInCache(article); + this.storeArticleInCache(article).catch(e => Cu.reportError("Error storing article in cache: " + e)); }, getStateForParseOnLoad: function Reader_getStateForParseOnLoad() { @@ -154,30 +161,28 @@ let Reader = { let request = { url: url, callbacks: [callback] }; this._requests[url] = request; - try { - this.log("parseDocumentFromURL: " + url); + let uri = Services.io.newURI(url, null, null); - // First, try to find a cached parsed article in the DB - this.getArticleFromCache(url, function(article) { - if (article) { - this.log("Page found in cache, return article immediately"); - this._runCallbacksAndFinish(request, article); - return; - } + // First, try to find a parsed article in the cache. + this.getArticleFromCache(uri).then(article => { + if (article) { + this.log("Page found in cache, return article immediately"); + this._runCallbacksAndFinish(request, article); + return; + } - if (!this._requests) { - this.log("Reader has been destroyed, abort"); - return; - } + if (!this._requests) { + this.log("Reader has been destroyed, abort"); + return; + } - // Article hasn't been found in the cache DB, we need to - // download the page and parse the article out of it. - this._downloadAndParseDocument(url, request); - }.bind(this)); - } catch (e) { - this.log("Error parsing document from URL: " + e); + // Article hasn't been found in the cache, we need to + // download the page and parse the article out of it. + this._downloadAndParseDocument(url, request); + }, e => { + Cu.reportError("Error trying to get article from cache: " + e); this._runCallbacksAndFinish(request, null); - } + }); }, getArticleForTab: function Reader_getArticleForTab(tabId, url, callback) { @@ -194,109 +199,81 @@ let Reader = { this.parseDocumentFromURL(url, callback); }, - parseDocumentFromTab: function(tabId, callback) { - try { - this.log("parseDocumentFromTab: " + tabId); + parseDocumentFromTab: function (tab, callback) { + let uri = tab.browser.currentURI; + if (!this._shouldCheckUri(uri)) { + callback(null); + return; + } - let tab = BrowserApp.getTabForId(tabId); - let url = tab.browser.contentWindow.location.href; - let uri = Services.io.newURI(url, null, null); - - if (!this._shouldCheckUri(uri)) { - callback(null); + // First, try to find a parsed article in the cache. + this.getArticleFromCache(uri).then(article => { + if (article) { + this.log("Page found in cache, return article immediately"); + callback(article); return; } - // First, try to find a cached parsed article in the DB - this.getArticleFromCache(url, function(article) { - if (article) { - this.log("Page found in cache, return article immediately"); - callback(article); + let doc = tab.browser.contentWindow.document; + this._readerParse(uri, doc, article => { + if (!article) { + this.log("Failed to parse page"); + callback(null); return; } - - let doc = tab.browser.contentWindow.document; - this._readerParse(uri, doc, function (article) { - if (!article) { - this.log("Failed to parse page"); - callback(null); - return; - } - - callback(article); - }.bind(this)); - }.bind(this)); - } catch (e) { - this.log("Error parsing document from tab: " + e); + callback(article); + }); + }, e => { + Cu.reportError("Error trying to get article from cache: " + e); callback(null); + }); + }, + + /** + * Retrieves an article from the cache given an article URI. + * + * @param uri The article URI. + * @return Promise + * @resolve JS object representing the article, or null if no article is found. + * @rejects OS.File.Error + */ + getArticleFromCache: Task.async(function* (uri) { + let path = this._toHashedPath(uri.specIgnoringRef); + try { + let array = yield OS.File.read(path); + return JSON.parse(new TextDecoder().decode(array)); + } catch (e if e instanceof OS.File.Error && e.becauseNoSuchFile) { + return null; } - }, + }), - getArticleFromCache: function Reader_getArticleFromCache(url, callback) { - this._getCacheDB(function(cacheDB) { - if (!cacheDB) { - callback(false); - return; - } + /** + * Stores an article in the cache. + * + * @param article JS object representing article. + * @return Promise + * @resolve When the article is stored. + * @rejects OS.File.Error + */ + storeArticleInCache: Task.async(function* (article) { + let array = new TextEncoder().encode(JSON.stringify(article)); + let path = this._toHashedPath(article.url); + yield this._ensureCacheDir(); + yield OS.File.writeAtomic(path, array, { tmpPath: path + ".tmp" }); + }), - let transaction = cacheDB.transaction(cacheDB.objectStoreNames); - let articles = transaction.objectStore(cacheDB.objectStoreNames[0]); - - let request = articles.get(url); - - request.onerror = function(event) { - this.log("Error getting article from the cache DB: " + url); - callback(null); - }.bind(this); - - request.onsuccess = function(event) { - this.log("Got article from the cache DB: " + event.target.result); - callback(event.target.result); - }.bind(this); - }.bind(this)); - }, - - storeArticleInCache: function Reader_storeArticleInCache(article) { - this._getCacheDB(function(cacheDB) { - if (!cacheDB) { - return; - } - - let transaction = cacheDB.transaction(cacheDB.objectStoreNames, "readwrite"); - let articles = transaction.objectStore(cacheDB.objectStoreNames[0]); - - let request = articles.add(article); - - request.onerror = function(event) { - this.log("Error storing article in the cache DB: " + article.url); - }.bind(this); - - request.onsuccess = function(event) { - this.log("Stored article in the cache DB: " + article.url); - }.bind(this); - }.bind(this)); - }, - - removeArticleFromCache: function Reader_removeArticleFromCache(url) { - this._getCacheDB(function(cacheDB) { - if (!cacheDB) { - return; - } - - let transaction = cacheDB.transaction(cacheDB.objectStoreNames, "readwrite"); - let articles = transaction.objectStore(cacheDB.objectStoreNames[0]); - - let request = articles.delete(url); - - request.onerror = function(event) { - this.log("Error removing article from the cache DB: " + url); - }.bind(this); - - request.onsuccess = function(event) { - this.log("Removed article from the cache DB: " + url); - }.bind(this); - }.bind(this)); - }, + /** + * Removes an article from the cache given an article URI. + * + * @param uri The article URI. + * @return Promise + * @resolve When the article is removed. + * @rejects OS.File.Error + */ + removeArticleFromCache: Task.async(function* (uri) { + let path = this._toHashedPath(uri.specIgnoringRef); + yield OS.File.remove(path); + }), uninit: function Reader_uninit() { Services.prefs.removeObserver("reader.parse-on-load.", this); @@ -312,11 +289,6 @@ let Reader = { } } delete this._requests; - - if (this._cacheDB) { - this._cacheDB.close(); - delete this._cacheDB; - } }, log: function(msg) { @@ -374,7 +346,7 @@ let Reader = { doc: new XMLSerializer().serializeToString(doc) }); } catch (e) { - dump("Reader: could not build Readability arguments: " + e); + Cu.reportError("Reader: could not build Readability arguments: " + e); callback(null); } }, @@ -406,7 +378,7 @@ let Reader = { browser.webNavigation.allowMetaRedirects = true; browser.webNavigation.allowPlugins = false; - browser.addEventListener("DOMContentLoaded", function (event) { + browser.addEventListener("DOMContentLoaded", event => { let doc = event.originalTarget; // ignore on frames and other documents @@ -423,7 +395,7 @@ let Reader = { } callback(doc); - }.bind(this)); + }); browser.loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_NONE, null, null, null); @@ -435,7 +407,7 @@ let Reader = { try { this.log("Needs to fetch page, creating request: " + url); - request.browser = this._downloadDocument(url, function(doc) { + request.browser = this._downloadDocument(url, doc => { this.log("Finished loading page: " + doc); if (!doc) { @@ -447,7 +419,7 @@ let Reader = { this.log("Parsing response with Readability"); let uri = Services.io.newURI(url, null, null); - this._readerParse(uri, doc, function (article) { + this._readerParse(uri, doc, article => { // Delete reference to the browser element as we've finished parsing. let browser = request.browser; if (browser) { @@ -462,47 +434,57 @@ let Reader = { } this.log("Parsing has been successful"); - this._runCallbacksAndFinish(request, article); - }.bind(this)); - }.bind(this)); + }); + }); } catch (e) { this.log("Error downloading and parsing document: " + e); this._runCallbacksAndFinish(request, null); } }, - _getCacheDB: function Reader_getCacheDB(callback) { - if (this._cacheDB) { - callback(this._cacheDB); - return; - } + get _cryptoHash() { + delete this._cryptoHash; + return this._cryptoHash = Cc["@mozilla.org/security/hash;1"].createInstance(Ci.nsICryptoHash); + }, - let request = window.indexedDB.open("about:reader", this.DB_VERSION); + get _unicodeConverter() { + delete this._unicodeConverter; + this._unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] + .createInstance(Ci.nsIScriptableUnicodeConverter); + this._unicodeConverter.charset = "utf8"; + return this._unicodeConverter; + }, - request.onerror = function(event) { - this.log("Error connecting to the cache DB"); - this._cacheDB = null; - callback(null); - }.bind(this); + /** + * Calculate the hashed path for a stripped article URL. + * + * @param url The article URL. This should have referrers removed. + * @return The file path to the cached article. + */ + _toHashedPath: function (url) { + let value = this._unicodeConverter.convertToByteArray(url); + this._cryptoHash.init(this._cryptoHash.MD5); + this._cryptoHash.update(value, value.length); - request.onsuccess = function(event) { - this.log("Successfully connected to the cache DB"); - this._cacheDB = event.target.result; - callback(this._cacheDB); - }.bind(this); + let hash = CommonUtils.encodeBase32(this._cryptoHash.finish(false)); + let fileName = hash.substring(0, hash.indexOf("=")) + ".json"; + return OS.Path.join(OS.Constants.Path.profileDir, "readercache", fileName); + }, - request.onupgradeneeded = function(event) { - this.log("Database schema upgrade from " + - event.oldVersion + " to " + event.newVersion); - - let cacheDB = event.target.result; - - // Create the articles object store - this.log("Creating articles object store"); - cacheDB.createObjectStore("articles", { keyPath: "url" }); - - this.log("Database upgrade done: " + this.DB_VERSION); - }.bind(this); + /** + * Ensures the cache directory exists. + * + * @return Promise + * @resolves When the cache directory exists. + * @rejects OS.File.Error + */ + _ensureCacheDir: function () { + let dir = OS.Path.join(OS.Constants.Path.profileDir, "readercache"); + return OS.File.exists(dir).then(exists => { + if (!exists) { + return OS.File.makeDir(dir); + } + }); } }; diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 6dd5d269211..4f84f3724d0 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -4225,7 +4225,7 @@ Tab.prototype = { return; // Once document is fully loaded, parse it - Reader.parseDocumentFromTab(this.id, function (article) { + Reader.parseDocumentFromTab(this, function (article) { // The loaded page may have changed while we were parsing the document. // Make sure we've got the current one. let uri = this.browser.currentURI; From 1ed30aacead3eefd5d2e93c68c80f63b33f525f1 Mon Sep 17 00:00:00 2001 From: Margaret Leibovic Date: Tue, 4 Nov 2014 13:34:47 -0800 Subject: [PATCH 011/153] Bug 1007409 - Test for reading list cache. r=rnewman --- mobile/android/base/tests/robocop.ini | 1 + .../android/base/tests/robocop_article.html | 16 +++++ .../base/tests/testReadingListCache.java | 8 +++ .../base/tests/testReadingListCache.js | 68 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 mobile/android/base/tests/robocop_article.html create mode 100644 mobile/android/base/tests/testReadingListCache.java create mode 100644 mobile/android/base/tests/testReadingListCache.js diff --git a/mobile/android/base/tests/robocop.ini b/mobile/android/base/tests/robocop.ini index f4da9b48122..8c19d6aafaa 100644 --- a/mobile/android/base/tests/robocop.ini +++ b/mobile/android/base/tests/robocop.ini @@ -72,6 +72,7 @@ skip-if = processor == "x86" # bug 957185 for x86, bug 1001657 for 2.3 skip-if = android_version == "10" || processor == "x86" # [testReaderMode] # see bug 913254, 936224 +[testReadingListCache] [testReadingListProvider] [testSearchHistoryProvider] [testSearchSuggestions] diff --git a/mobile/android/base/tests/robocop_article.html b/mobile/android/base/tests/robocop_article.html new file mode 100644 index 00000000000..f34cbece4e1 --- /dev/null +++ b/mobile/android/base/tests/robocop_article.html @@ -0,0 +1,16 @@ + + + +Article title + + + +
Site header
+
+

Article title

+

by Jane Doe

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.

+

Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.

+
+ + diff --git a/mobile/android/base/tests/testReadingListCache.java b/mobile/android/base/tests/testReadingListCache.java new file mode 100644 index 00000000000..e47ba555fc1 --- /dev/null +++ b/mobile/android/base/tests/testReadingListCache.java @@ -0,0 +1,8 @@ +package org.mozilla.gecko.tests; + + +public class testReadingListCache extends JavascriptTest { + public testReadingListCache() { + super("testReadingListCache.js"); + } +} diff --git a/mobile/android/base/tests/testReadingListCache.js b/mobile/android/base/tests/testReadingListCache.js new file mode 100644 index 00000000000..d9b7e1a9113 --- /dev/null +++ b/mobile/android/base/tests/testReadingListCache.js @@ -0,0 +1,68 @@ +// -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +const { utils: Cu } = Components; + +Cu.import("resource://gre/modules/osfile.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +// Values from robocop_article.html +const ARTICLE = { + url: "http://mochi.test:8888/tests/robocop/robocop_article.html", + title: "Article title", + byline: "by Jane Doe", + excerpt: "This is the article description.", + length: 1931, + content: "Lorem ipsum..." // This test doesn't actually compare content strings +}; + +const ARTICLE_URI = Services.io.newURI(ARTICLE.url, null, null); + +add_task(function test_article_not_found() { + let Reader = Services.wm.getMostRecentWindow("navigator:browser").Reader; + + let article = yield Reader.getArticleFromCache(ARTICLE_URI); + do_check_eq(article, null); +}); + +add_task(function test_store_article() { + let Reader = Services.wm.getMostRecentWindow("navigator:browser").Reader; + + yield Reader.storeArticleInCache(ARTICLE); + + let article = yield Reader.getArticleFromCache(ARTICLE_URI); + checkArticle(article); +}); + +add_task(function test_remove_article() { + let Reader = Services.wm.getMostRecentWindow("navigator:browser").Reader; + + yield Reader.removeArticleFromCache(ARTICLE_URI); + + let article = yield Reader.getArticleFromCache(ARTICLE_URI); + do_check_eq(article, null); +}); + +add_test(function test_parse_article() { + let Reader = Services.wm.getMostRecentWindow("navigator:browser").Reader; + + Reader.parseDocumentFromURL(ARTICLE.url, function parseCallback(article) { + checkArticle(article); + run_next_test(); + }); +}); + +function checkArticle(article) { + do_check_neq(article, null); + do_check_neq(article.content, null); + do_check_eq(article.url, ARTICLE.url); + do_check_eq(article.title, ARTICLE.title); + do_check_eq(article.byline, ARTICLE.byline); + do_check_eq(article.excerpt, ARTICLE.excerpt); + do_check_eq(article.length, ARTICLE.length); +} + +run_next_test(); From 459d47ca20ccadda58cb2858cc9b501a67c726af Mon Sep 17 00:00:00 2001 From: Chris Kitching Date: Fri, 19 Sep 2014 18:10:01 -0700 Subject: [PATCH 012/153] Bug 961600: Use the favicon cache for search engine icons. r=rnewman --- mobile/android/base/GeckoAppShell.java | 20 +++++---- .../favicons/decoders/FaviconDecoder.java | 44 ------------------- .../preferences/SearchEnginePreference.java | 30 ++++++++++--- 3 files changed, 37 insertions(+), 57 deletions(-) diff --git a/mobile/android/base/GeckoAppShell.java b/mobile/android/base/GeckoAppShell.java index 86ab8703788..3f338e40a39 100644 --- a/mobile/android/base/GeckoAppShell.java +++ b/mobile/android/base/GeckoAppShell.java @@ -31,6 +31,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import org.mozilla.gecko.AppConstants.Versions; +import org.mozilla.gecko.favicons.Favicons; import org.mozilla.gecko.favicons.OnFaviconLoadedListener; import org.mozilla.gecko.favicons.decoders.FaviconDecoder; import org.mozilla.gecko.gfx.BitmapUtils; @@ -806,21 +807,24 @@ public class GeckoAppShell // This is the entry point from nsIShellService. @WrapElementForJNI static void createShortcut(final String aTitle, final String aURI, final String aIconData) { - ThreadUtils.postToBackgroundThread(new Runnable() { - @Override - public void run() { - // TODO: use the cache. Bug 961600. - Bitmap icon = FaviconDecoder.getMostSuitableBitmapFromDataURI(aIconData, getPreferredIconSize()); - GeckoAppShell.doCreateShortcut(aTitle, aURI, icon); + // We have the favicon data (base64) decoded on the background thread, callback here, then + // call the other createShortcut method with the decoded favicon. + // This is slightly contrived, but makes the images available to the favicon cache. + Favicons.getSizedFavicon(getContext(), aURI, aIconData, Integer.MAX_VALUE, 0, + new OnFaviconLoadedListener() { + @Override + public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) { + createShortcut(aTitle, url, favicon); + } } - }); + ); } public static void createShortcut(final String aTitle, final String aURI, final Bitmap aBitmap) { ThreadUtils.postToBackgroundThread(new Runnable() { @Override public void run() { - GeckoAppShell.doCreateShortcut(aTitle, aURI, aBitmap); + doCreateShortcut(aTitle, aURI, aBitmap); } }); } diff --git a/mobile/android/base/favicons/decoders/FaviconDecoder.java b/mobile/android/base/favicons/decoders/FaviconDecoder.java index 4bd0da212af..a1a8e4c354a 100644 --- a/mobile/android/base/favicons/decoders/FaviconDecoder.java +++ b/mobile/android/base/favicons/decoders/FaviconDecoder.java @@ -151,50 +151,6 @@ public class FaviconDecoder { return decodeFavicon(buffer, 0, buffer.length); } - /** - * Returns the smallest bitmap in the icon represented by the provided - * data: URI that's larger than the desired width, or the largest if - * there is no larger icon. - * - * Returns null if no bitmap could be extracted. - * - * Bug 961600: we shouldn't be doing all of this work. The favicon cache - * should be used, and will give us the right size icon. - */ - public static Bitmap getMostSuitableBitmapFromDataURI(String iconURI, int desiredWidth) { - LoadFaviconResult result = FaviconDecoder.decodeDataURI(iconURI); - if (result == null) { - // Nothing we can do. - Log.w(LOG_TAG, "Unable to decode icon URI."); - return null; - } - - final Iterator bitmaps = result.getBitmaps(); - if (!bitmaps.hasNext()) { - Log.w(LOG_TAG, "No bitmaps in decoded icon."); - return null; - } - - Bitmap bitmap = bitmaps.next(); - if (!bitmaps.hasNext()) { - // We're done! There was only one, so this is as big as it gets. - return bitmap; - } - - // Find a bitmap of the most suitable size. - int currentWidth = bitmap.getWidth(); - while ((currentWidth < desiredWidth) && - bitmaps.hasNext()) { - final Bitmap b = bitmaps.next(); - if (b.getWidth() > currentWidth) { - currentWidth = b.getWidth(); - bitmap = b; - } - } - - return bitmap; - } - /** * Iterator to hold a single bitmap. */ diff --git a/mobile/android/base/preferences/SearchEnginePreference.java b/mobile/android/base/preferences/SearchEnginePreference.java index ce0fab15fd1..bf0946a79f2 100644 --- a/mobile/android/base/preferences/SearchEnginePreference.java +++ b/mobile/android/base/preferences/SearchEnginePreference.java @@ -8,6 +8,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.mozilla.gecko.R; import org.mozilla.gecko.favicons.Favicons; +import org.mozilla.gecko.favicons.OnFaviconLoadedListener; import org.mozilla.gecko.favicons.decoders.FaviconDecoder; import org.mozilla.gecko.util.ThreadUtils; import org.mozilla.gecko.widget.FaviconView; @@ -34,6 +35,7 @@ public class SearchEnginePreference extends CustomListPreference { // The bitmap backing the drawable above - needed separately for the FaviconView. private Bitmap mIconBitmap; + private final Object bitmapLock = new Object(); private FaviconView mFaviconView; @@ -55,9 +57,16 @@ public class SearchEnginePreference extends CustomListPreference { protected void onBindView(View view) { super.onBindView(view); - // Set the icon in the FaviconView. - mFaviconView = ((FaviconView) view.findViewById(R.id.search_engine_icon)); - mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString()); + // We synchronise to avoid a race condition between this and the favicon loading callback in + // setSearchEngineFromJSON. + synchronized (bitmapLock) { + // Set the icon in the FaviconView. + mFaviconView = ((FaviconView) view.findViewById(R.id.search_engine_icon)); + + if (mIconBitmap != null) { + mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString()); + } + } } @Override @@ -161,9 +170,20 @@ public class SearchEnginePreference extends CustomListPreference { } } - // TODO: use the cache. Bug 961600. - mIconBitmap = FaviconDecoder.getMostSuitableBitmapFromDataURI(iconURI, desiredWidth); + Favicons.getSizedFavicon(getContext(), mIdentifier, iconURI, desiredWidth, 0, + new OnFaviconLoadedListener() { + @Override + public void onFaviconLoaded(String url, String faviconURL, Bitmap favicon) { + synchronized (bitmapLock) { + mIconBitmap = favicon; + if (mFaviconView != null) { + mFaviconView.updateAndScaleImage(mIconBitmap, getTitle().toString()); + } + } + } + } + ); } catch (IllegalArgumentException e) { Log.e(LOGTAG, "IllegalArgumentException creating Bitmap. Most likely a zero-length bitmap.", e); } catch (NullPointerException e) { From 801d787ceaa45fba7f2f1704c1fdabfa8cbce98b Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Tue, 4 Nov 2014 08:34:56 -0800 Subject: [PATCH 013/153] Bug 1088831 - Count storebuffer compactions, r=jonco --HG-- extra : rebase_source : e9bb802708fe5813377ce09f487a6304a8c265d7 --- js/src/gc/Statistics.cpp | 3 +++ js/src/gc/Statistics.h | 1 + js/src/gc/StoreBuffer.cpp | 9 +++++++++ js/src/gc/StoreBuffer.h | 6 ++++++ 4 files changed, 19 insertions(+) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index af9602d1a23..9624251b986 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -385,6 +385,7 @@ Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) ss.appendNumber("Total Zones", "%d", "", zoneStats.zoneCount); ss.appendNumber("Total Compartments", "%d", "", zoneStats.compartmentCount); ss.appendNumber("Minor GCs", "%d", "", counts[STAT_MINOR_GC]); + ss.appendNumber("Store Buffer Compactions", "%d", "", counts[STAT_COMPACT_STOREBUFFER]); ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal)); @@ -476,6 +477,7 @@ Statistics::formatDescription() Zones Collected: %d of %d\n\ Compartments Collected: %d of %d\n\ MinorGCs since last GC: %d\n\ + Store Buffer Compactions: %d\n\ MMU 20ms:%.1f%%; 50ms:%.1f%%\n\ SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\ HeapSize: %.3f MiB\n\ @@ -491,6 +493,7 @@ Statistics::formatDescription() zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.collectedCompartmentCount, zoneStats.compartmentCount, counts[STAT_MINOR_GC], + counts[STAT_COMPACT_STOREBUFFER], mmu20 * 100., mmu50 * 100., t(sccTotal), t(sccLongest), double(preBytes) / 1024. / 1024., diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 933d449aefe..88c7b338ee9 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -78,6 +78,7 @@ enum Stat { STAT_NEW_CHUNK, STAT_DESTROY_CHUNK, STAT_MINOR_GC, + STAT_COMPACT_STOREBUFFER, STAT_LIMIT }; diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index 0587e434f03..c9bb351897f 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -19,6 +19,12 @@ using namespace js; using namespace js::gc; using mozilla::ReentrancyGuard; +gcstats::Statistics& +StoreBuffer::stats() +{ + return runtime_->gc.stats; +} + /*** Edges ***/ void @@ -110,6 +116,8 @@ template void StoreBuffer::MonoTypeBuffer::compactRemoveDuplicates(StoreBuffer *owner) { + Statistics& stats = owner->stats(); + typedef HashSet DedupSet; DedupSet duplicates; @@ -130,6 +138,7 @@ StoreBuffer::MonoTypeBuffer::compactRemoveDuplicates(StoreBuffer *owner) storage_->release(insert.mark()); duplicates.clear(); + stats.count(gcstats::STAT_COMPACT_STOREBUFFER); } template diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h index b5b19fb6345..2a1458c71dc 100644 --- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -22,6 +22,10 @@ namespace js { +namespace gcstats { +struct Statistics; +} + MOZ_NORETURN void CrashAtUnhandlableOOM(const char *reason); @@ -489,6 +493,8 @@ class StoreBuffer putFromAnyThread(bufferGeneric, CallbackRef(callback, key, data)); } + gcstats::Statistics& stats(); + /* Methods to mark the source of all edges in the store buffer. */ void markAll(JSTracer *trc); void markValues(JSTracer *trc) { bufferVal.mark(this, trc); } From 9a6efa67bc98c9d244f0e194ea39d1ef83feafab Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Tue, 4 Nov 2014 13:23:10 -0800 Subject: [PATCH 014/153] Bug 1088831 - Account for the storebuffer compaction time, r=jonco --HG-- extra : rebase_source : c6dc383197f0abd01eb9bdaf49fb2ac9d370a1b4 --- js/src/gc/Statistics.cpp | 25 ++++++++++++++++++++----- js/src/gc/Statistics.h | 9 +++++++++ js/src/gc/StoreBuffer.cpp | 21 +++++++++++++-------- js/src/gc/StoreBuffer.h | 2 +- js/src/gc/Verifier.cpp | 5 ++++- js/src/jsgc.cpp | 2 ++ 6 files changed, 49 insertions(+), 15 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 9624251b986..5c415c03b87 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -326,6 +326,9 @@ static const PhaseInfo phases[] = { { PHASE_COMPACT_UPDATE, "Compact Update", PHASE_COMPACT, }, { PHASE_COMPACT_UPDATE_GRAY, "Compact Update Gray", PHASE_COMPACT_UPDATE, }, { PHASE_GC_END, "End Callback", PHASE_NO_PARENT }, + { PHASE_MINOR_GC, "Minor GC", PHASE_NO_PARENT }, + { PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC, "Compact Store Buffers", PHASE_MINOR_GC }, + { PHASE_COMPACT_STOREBUFFER_NO_PARENT, "Compact Store Buffers (toplevel)", PHASE_NO_PARENT }, { PHASE_LIMIT, nullptr, PHASE_NO_PARENT } }; @@ -386,6 +389,7 @@ Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) ss.appendNumber("Total Compartments", "%d", "", zoneStats.compartmentCount); ss.appendNumber("Minor GCs", "%d", "", counts[STAT_MINOR_GC]); ss.appendNumber("Store Buffer Compactions", "%d", "", counts[STAT_COMPACT_STOREBUFFER]); + ss.appendNumber("Store Buffer Overflows", "%d", "", counts[STAT_STOREBUFFER_OVERFLOW]); ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal)); @@ -478,6 +482,7 @@ Statistics::formatDescription() Compartments Collected: %d of %d\n\ MinorGCs since last GC: %d\n\ Store Buffer Compactions: %d\n\ + Store Buffer Overflows: %d\n\ MMU 20ms:%.1f%%; 50ms:%.1f%%\n\ SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\ HeapSize: %.3f MiB\n\ @@ -494,6 +499,7 @@ Statistics::formatDescription() zoneStats.collectedCompartmentCount, zoneStats.compartmentCount, counts[STAT_MINOR_GC], counts[STAT_COMPACT_STOREBUFFER], + counts[STAT_STOREBUFFER_OVERFLOW], mmu20 * 100., mmu50 * 100., t(sccTotal), t(sccLongest), double(preBytes) / 1024. / 1024., @@ -643,6 +649,8 @@ Statistics::Statistics(JSRuntime *rt) { PodArrayZero(phaseTotals); PodArrayZero(counts); + PodArrayZero(phaseStartTimes); + PodArrayZero(phaseTimes); char *env = getenv("MOZ_GCTIMER"); if (!env || strcmp(env, "none") == 0) { @@ -726,9 +734,6 @@ Statistics::printStats() void Statistics::beginGC(JSGCInvocationKind kind) { - PodArrayZero(phaseStartTimes); - PodArrayZero(phaseTimes); - slices.clearAndFree(); sccTimes.clearAndFree(); gckind = kind; @@ -770,6 +775,11 @@ Statistics::endGC() if (fp) printStats(); + + // Clear the timers at the end of a GC because we accumulate time for some + // phases (eg storebuffer compaction) during the mutator's run. + PodArrayZero(phaseStartTimes); + PodArrayZero(phaseTimes); } void @@ -837,6 +847,9 @@ Statistics::beginPhase(Phase phase) Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT; MOZ_ASSERT(phaseNestingDepth < MAX_NESTING); MOZ_ASSERT_IF(gcDepth == 1, phases[phase].parent == parent); + MOZ_ASSERT_IF(phase == PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC, parent == PHASE_MINOR_GC); + MOZ_ASSERT_IF(phase == PHASE_COMPACT_STOREBUFFER_NO_PARENT, parent == PHASE_NO_PARENT); + phaseNesting[phaseNestingDepth] = phase; phaseNestingDepth++; #endif @@ -849,8 +862,10 @@ Statistics::endPhase(Phase phase) { phaseNestingDepth--; - int64_t t = PRMJ_Now() - phaseStartTimes[phase]; - slices.back().phaseTimes[phase] += t; + int64_t now = PRMJ_Now(); + int64_t t = now - phaseStartTimes[phase]; + if (!slices.empty()) + slices.back().phaseTimes[phase] += t; phaseTimes[phase] += t; phaseStartTimes[phase] = 0; } diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 88c7b338ee9..6793d024f61 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -70,6 +70,9 @@ enum Phase { PHASE_COMPACT_UPDATE, PHASE_COMPACT_UPDATE_GRAY, PHASE_GC_END, + PHASE_MINOR_GC, + PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC, + PHASE_COMPACT_STOREBUFFER_NO_PARENT, PHASE_LIMIT }; @@ -78,8 +81,14 @@ enum Stat { STAT_NEW_CHUNK, STAT_DESTROY_CHUNK, STAT_MINOR_GC, + + // Number of times the storebuffers were compacted STAT_COMPACT_STOREBUFFER, + // Number of times a 'put' into a storebuffer overflowed, triggering a + // compaction + STAT_STOREBUFFER_OVERFLOW, + STAT_LIMIT }; diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index c9bb351897f..4462549b115 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -99,6 +99,8 @@ StoreBuffer::MonoTypeBuffer::handleOverflow(StoreBuffer *owner) * Compact the buffer now, and if that fails to free enough space then * trigger a minor collection. */ + gcstats::AutoPhase ap(owner->stats(), PHASE_COMPACT_STOREBUFFER_NO_PARENT); + owner->stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW); compact(owner); if (isLowOnSpace()) owner->setAboutToOverflow(); @@ -107,8 +109,10 @@ StoreBuffer::MonoTypeBuffer::handleOverflow(StoreBuffer *owner) * A minor GC has already been triggered, so there's no point * compacting unless the buffer is totally full. */ - if (storage_->availableInCurrentChunk() < sizeof(T)) - maybeCompact(owner); + if (storage_->availableInCurrentChunk() < sizeof(T)) { + owner->stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW); + maybeCompact(owner, PHASE_COMPACT_STOREBUFFER_NO_PARENT); + } } } @@ -116,8 +120,6 @@ template void StoreBuffer::MonoTypeBuffer::compactRemoveDuplicates(StoreBuffer *owner) { - Statistics& stats = owner->stats(); - typedef HashSet DedupSet; DedupSet duplicates; @@ -138,7 +140,7 @@ StoreBuffer::MonoTypeBuffer::compactRemoveDuplicates(StoreBuffer *owner) storage_->release(insert.mark()); duplicates.clear(); - stats.count(gcstats::STAT_COMPACT_STOREBUFFER); + owner->stats().count(gcstats::STAT_COMPACT_STOREBUFFER); } template @@ -152,11 +154,13 @@ StoreBuffer::MonoTypeBuffer::compact(StoreBuffer *owner) template void -StoreBuffer::MonoTypeBuffer::maybeCompact(StoreBuffer *owner) +StoreBuffer::MonoTypeBuffer::maybeCompact(StoreBuffer *owner, gcstats::Phase phase) { MOZ_ASSERT(storage_); - if (storage_->used() != usedAtLastCompact_) + if (storage_->used() != usedAtLastCompact_) { + gcstats::AutoPhase ap(owner->stats(), phase); compact(owner); + } } template @@ -168,7 +172,8 @@ StoreBuffer::MonoTypeBuffer::mark(StoreBuffer *owner, JSTracer *trc) if (!storage_) return; - maybeCompact(owner); + maybeCompact(owner, PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC); + for (LifoAlloc::Enum e(*storage_); !e.empty(); e.popFront()) { T *edge = e.get(); edge->mark(trc); diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h index 2a1458c71dc..94e4b88e713 100644 --- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -144,7 +144,7 @@ class StoreBuffer virtual void compact(StoreBuffer *owner); /* Compacts if any entries have been added since the last compaction. */ - void maybeCompact(StoreBuffer *owner); + void maybeCompact(StoreBuffer *owner, gcstats::Phase phase); /* Add one item to the buffer. */ void put(StoreBuffer *owner, const T &t) { diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index f092b5e116a..0428252db9b 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -503,7 +503,10 @@ js::gc::GCRuntime::endVerifyPostBarriers() if (!edges.init()) goto oom; trc->edges = &edges; - storeBuffer.markAll(trc); + { + gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); + storeBuffer.markAll(trc); + } /* Walk the heap to find any edges not the the |edges| set. */ trc->setTraceCallback(PostVerifierVisitEdge); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 1ffacbdb245..80cd4fb77f7 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -6217,6 +6217,7 @@ void GCRuntime::minorGC(JS::gcreason::Reason reason) { #ifdef JSGC_GENERATIONAL + gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); minorGCRequested = false; TraceLogger *logger = TraceLoggerForMainThread(rt); AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); @@ -6231,6 +6232,7 @@ GCRuntime::minorGC(JSContext *cx, JS::gcreason::Reason reason) // Alternate to the runtime-taking form above which allows marking type // objects as needing pretenuring. #ifdef JSGC_GENERATIONAL + gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); minorGCRequested = false; TraceLogger *logger = TraceLoggerForMainThread(rt); AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); From 3fde6a2537c0e269916d3143734db8cf26b35c54 Mon Sep 17 00:00:00 2001 From: Steve Fink Date: Tue, 4 Nov 2014 13:23:13 -0800 Subject: [PATCH 015/153] Bug 1088831 - Track mutator vs GC time in specified intervals, r=jonco --HG-- extra : rebase_source : 44313ac31315dbebde12bd76ade8f0cfb4d7ce5a --- js/src/gc/Statistics.cpp | 53 +++++++++++++++++++++++++++++++++++++-- js/src/gc/Statistics.h | 15 ++++++++--- js/src/gc/StoreBuffer.cpp | 11 ++++---- js/src/gc/StoreBuffer.h | 2 +- js/src/shell/js.cpp | 45 +++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+), 11 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 5c415c03b87..bc897c8bc46 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -27,6 +27,7 @@ using namespace js::gc; using namespace js::gcstats; using mozilla::PodArrayZero; +using mozilla::PodZero; /* Except for the first and last, slices of less than 10ms are not reported. */ static const int64_t SLICE_MIN_REPORT_TIME = 10 * PRMJ_USEC_PER_MSEC; @@ -283,6 +284,7 @@ struct PhaseInfo static const Phase PHASE_NO_PARENT = PHASE_LIMIT; static const PhaseInfo phases[] = { + { PHASE_MUTATOR, "Mutator Running", PHASE_NO_PARENT }, { PHASE_GC_BEGIN, "Begin Callback", PHASE_NO_PARENT }, { PHASE_WAIT_BACKGROUND_THREAD, "Wait Background Thread", PHASE_NO_PARENT }, { PHASE_MARK_DISCARD_CODE, "Mark Discard Code", PHASE_NO_PARENT }, @@ -642,6 +644,8 @@ Statistics::Statistics(JSRuntime *rt) fullFormat(false), gcDepth(0), nonincrementalReason(nullptr), + timingMutator(false), + timedGCStart(0), preBytes(0), maxPauseInInterval(0), phaseNestingDepth(0), @@ -778,8 +782,8 @@ Statistics::endGC() // Clear the timers at the end of a GC because we accumulate time for some // phases (eg storebuffer compaction) during the mutator's run. - PodArrayZero(phaseStartTimes); - PodArrayZero(phaseTimes); + PodZero(&phaseStartTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN); + PodZero(&phaseTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN); } void @@ -836,12 +840,50 @@ Statistics::endSlice() PodArrayZero(counts); } +void +Statistics::startTimingMutator() +{ + MOZ_ASSERT(!timingMutator); + + // Should only be called from outside of GC + MOZ_ASSERT(phaseNestingDepth == 0); + + timingMutator = true; + timedGCTime = 0; + phaseStartTimes[PHASE_MUTATOR] = 0; + phaseTimes[PHASE_MUTATOR] = 0; + timedGCStart = 0; + + beginPhase(PHASE_MUTATOR); +} + +void +Statistics::stopTimingMutator(double &mutator_ms, double &gc_ms) +{ + MOZ_ASSERT(timingMutator); + + // Should only be called from outside of GC + MOZ_ASSERT(phaseNestingDepth == 1 && phaseNesting[0] == PHASE_MUTATOR); + + endPhase(PHASE_MUTATOR); + mutator_ms = t(phaseTimes[PHASE_MUTATOR]); + gc_ms = t(timedGCTime); + timingMutator = false; +} + void Statistics::beginPhase(Phase phase) { /* Guard against re-entry */ MOZ_ASSERT(!phaseStartTimes[phase]); + if (timingMutator) { + if (phaseNestingDepth == 1 && phaseNesting[0] == PHASE_MUTATOR) { + endPhase(PHASE_MUTATOR); + timedGCStart = PRMJ_Now(); + } + } + #ifdef DEBUG MOZ_ASSERT(phases[phase].index == phase); Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT; @@ -868,6 +910,13 @@ Statistics::endPhase(Phase phase) slices.back().phaseTimes[phase] += t; phaseTimes[phase] += t; phaseStartTimes[phase] = 0; + + if (timingMutator) { + if (phaseNestingDepth == 0 && phase != PHASE_MUTATOR) { + timedGCTime += now - timedGCStart; + beginPhase(PHASE_MUTATOR); + } + } } void diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 6793d024f61..cffc33a2b58 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -27,6 +27,7 @@ class GCParallelTask; namespace gcstats { enum Phase { + PHASE_MUTATOR, PHASE_GC_BEGIN, PHASE_WAIT_BACKGROUND_THREAD, PHASE_MARK_DISCARD_CODE, @@ -128,6 +129,9 @@ struct Statistics JS::gcreason::Reason reason); void endSlice(); + void startTimingMutator(); + void stopTimingMutator(double &mutator_ms, double &gc_ms); + void reset(const char *reason) { slices.back().resetReason = reason; } void nonincremental(const char *reason) { nonincrementalReason = reason; } @@ -189,6 +193,13 @@ struct Statistics /* Most recent time when the given phase started. */ int64_t phaseStartTimes[PHASE_LIMIT]; + /* Are we currently timing mutator vs GC time? */ + bool timingMutator; + + /* Bookkeeping for GC timings when timingMutator is true */ + int64_t timedGCStart; + int64_t timedGCTime; + /* Total time in a given phase for this GC. */ int64_t phaseTimes[PHASE_LIMIT]; @@ -204,12 +215,10 @@ struct Statistics /* Records the maximum GC pause in an API-controlled interval (in us). */ int64_t maxPauseInInterval; -#ifdef DEBUG /* Phases that are currently on stack. */ static const size_t MAX_NESTING = 8; Phase phaseNesting[MAX_NESTING]; -#endif - mozilla::DebugOnly phaseNestingDepth; + size_t phaseNestingDepth; /* Sweep times for SCCs of compartments. */ Vector sccTimes; diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index 4462549b115..23efde94426 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -10,6 +10,7 @@ #include "mozilla/Assertions.h" +#include "gc/Statistics.h" #include "vm/ArgumentsObject.h" #include "vm/ForkJoin.h" @@ -99,7 +100,7 @@ StoreBuffer::MonoTypeBuffer::handleOverflow(StoreBuffer *owner) * Compact the buffer now, and if that fails to free enough space then * trigger a minor collection. */ - gcstats::AutoPhase ap(owner->stats(), PHASE_COMPACT_STOREBUFFER_NO_PARENT); + gcstats::AutoPhase ap(owner->stats(), gcstats::PHASE_COMPACT_STOREBUFFER_NO_PARENT); owner->stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW); compact(owner); if (isLowOnSpace()) @@ -111,7 +112,7 @@ StoreBuffer::MonoTypeBuffer::handleOverflow(StoreBuffer *owner) */ if (storage_->availableInCurrentChunk() < sizeof(T)) { owner->stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW); - maybeCompact(owner, PHASE_COMPACT_STOREBUFFER_NO_PARENT); + maybeCompact(owner, gcstats::PHASE_COMPACT_STOREBUFFER_NO_PARENT); } } } @@ -154,11 +155,11 @@ StoreBuffer::MonoTypeBuffer::compact(StoreBuffer *owner) template void -StoreBuffer::MonoTypeBuffer::maybeCompact(StoreBuffer *owner, gcstats::Phase phase) +StoreBuffer::MonoTypeBuffer::maybeCompact(StoreBuffer *owner, int phase) { MOZ_ASSERT(storage_); if (storage_->used() != usedAtLastCompact_) { - gcstats::AutoPhase ap(owner->stats(), phase); + gcstats::AutoPhase ap(owner->stats(), static_cast(phase)); compact(owner); } } @@ -172,7 +173,7 @@ StoreBuffer::MonoTypeBuffer::mark(StoreBuffer *owner, JSTracer *trc) if (!storage_) return; - maybeCompact(owner, PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC); + maybeCompact(owner, gcstats::PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC); for (LifoAlloc::Enum e(*storage_); !e.empty(); e.popFront()) { T *edge = e.get(); diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h index 94e4b88e713..09276e8665a 100644 --- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -144,7 +144,7 @@ class StoreBuffer virtual void compact(StoreBuffer *owner); /* Compacts if any entries have been added since the last compaction. */ - void maybeCompact(StoreBuffer *owner, gcstats::Phase phase); + void maybeCompact(StoreBuffer *owner, int phase); /* Add one item to the buffer. */ void put(StoreBuffer *owner, const T &t) { diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index e063c9a984f..ffa6fc5bcf6 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1660,6 +1660,43 @@ Quit(JSContext *cx, unsigned argc, jsval *vp) return false; } +static bool +StartTimingMutator(JSContext *cx, unsigned argc, jsval *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() > 0) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, + JSSMSG_TOO_MANY_ARGS, "startTimingMutator"); + return false; + } + + cx->runtime()->gc.stats.startTimingMutator(); + args.rval().setUndefined(); + return true; +} + +static bool +StopTimingMutator(JSContext *cx, unsigned argc, jsval *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() > 0) { + JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, + JSSMSG_TOO_MANY_ARGS, "stopTimingMutator"); + return false; + } + + double mutator_ms, gc_ms; + cx->runtime()->gc.stats.stopTimingMutator(mutator_ms, gc_ms); + double total_ms = mutator_ms + gc_ms; + if (total_ms > 0) { + fprintf(gOutFile, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n", + mutator_ms, mutator_ms / total_ms * 100.0, gc_ms, gc_ms / total_ms * 100.0); + } + + args.rval().setUndefined(); + return true; +} + static const char * ToSource(JSContext *cx, MutableHandleValue vp, JSAutoByteString *bytes) { @@ -4458,6 +4495,14 @@ static const JSFunctionSpecWithHelp shell_functions[] = { " Throw if the first two arguments are not the same (both +0 or both -0,\n" " both NaN, or non-zero and ===)."), + JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0, +"startTimingMutator()", +" Start accounting time to mutator vs GC."), + + JS_FN_HELP("stopTimingMutator", StopTimingMutator, 0, 0, +"stopTimingMutator()", +" Stop accounting time to mutator vs GC and dump the results."), + JS_FN_HELP("throwError", ThrowError, 0, 0, "throwError()", " Throw an error from JS_ReportError."), From 0470baff841b5d241ec345f2be4e0c7bf23f0987 Mon Sep 17 00:00:00 2001 From: Benoit Girard Date: Fri, 31 Oct 2014 16:34:30 -0400 Subject: [PATCH 016/153] Bug 1089380 - Remove ClipRectInLayersCoordinates. r=mattwoodrow --HG-- extra : rebase_source : d20c08c804f45f2a6baee9063d0e73bf755054ae --- gfx/layers/Compositor.cpp | 16 ---------------- gfx/layers/Compositor.h | 9 --------- gfx/layers/composite/ContainerLayerComposite.cpp | 13 +------------ gfx/layers/composite/TiledContentHost.cpp | 9 --------- 4 files changed, 1 insertion(+), 46 deletions(-) diff --git a/gfx/layers/Compositor.cpp b/gfx/layers/Compositor.cpp index 6d8764c47a8..494f90adfa1 100644 --- a/gfx/layers/Compositor.cpp +++ b/gfx/layers/Compositor.cpp @@ -104,22 +104,6 @@ Compositor::DrawDiagnostics(DiagnosticFlags aFlags, aFlashCounter); } -RenderTargetRect -Compositor::ClipRectInLayersCoordinates(Layer* aLayer, RenderTargetIntRect aClip) const { - ContainerLayer* parent = aLayer->AsContainerLayer() ? aLayer->AsContainerLayer() : aLayer->GetParent(); - while (!parent->UseIntermediateSurface() && parent->GetParent()) { - parent = parent->GetParent(); - } - - RenderTargetIntPoint renderTargetOffset = RenderTargetIntRect::FromUntyped( - parent->GetEffectiveVisibleRegion().GetBounds()).TopLeft(); - - RenderTargetRect result; - aClip = aClip + renderTargetOffset; - result = RenderTargetRect(aClip.x, aClip.y, aClip.width, aClip.height); - return result; -} - void Compositor::DrawDiagnosticsInternal(DiagnosticFlags aFlags, const gfx::Rect& aVisibleRect, diff --git a/gfx/layers/Compositor.h b/gfx/layers/Compositor.h index 0168a767ff7..2ce3680b6a4 100644 --- a/gfx/layers/Compositor.h +++ b/gfx/layers/Compositor.h @@ -478,15 +478,6 @@ public: mScreenRotation = aRotation; } - // On b2g the clip rect is in the coordinate space of the physical screen - // independently of its rotation, while the coordinate space of the layers, - // on the other hand, depends on the screen orientation. - // This only applies to b2g as with other platforms, orientation is handled - // at the OS level rather than in Gecko. - // In addition, the clip rect needs to be offset by the rendering origin. - // This becomes important if intermediate surfaces are used. - RenderTargetRect ClipRectInLayersCoordinates(Layer* aLayer, RenderTargetIntRect aClip) const; - protected: void DrawDiagnosticsInternal(DiagnosticFlags aFlags, const gfx::Rect& aVisibleRect, diff --git a/gfx/layers/composite/ContainerLayerComposite.cpp b/gfx/layers/composite/ContainerLayerComposite.cpp index b854831be57..6a5bb578b09 100644 --- a/gfx/layers/composite/ContainerLayerComposite.cpp +++ b/gfx/layers/composite/ContainerLayerComposite.cpp @@ -163,18 +163,6 @@ ContainerPrepare(ContainerT* aContainer, continue; } - RenderTargetRect quad = layerToRender->GetLayer()-> - TransformRectToRenderTarget(LayerPixel::FromUntyped( - layerToRender->GetLayer()->GetEffectiveVisibleRegion().GetBounds())); - - Compositor* compositor = aManager->GetCompositor(); - if (!layerToRender->GetLayer()->AsContainerLayer() && - !quad.Intersects(compositor->ClipRectInLayersCoordinates(layerToRender->GetLayer(), clipRect)) && - !LayerHasCheckerboardingAPZC(layerToRender->GetLayer(), nullptr)) { - CULLING_LOG("Sublayer %p is clipped entirely\n", layerToRender->GetLayer()); - continue; - } - CULLING_LOG("Preparing sublayer %p\n", layerToRender->GetLayer()); layerToRender->Prepare(clipRect); @@ -328,6 +316,7 @@ RenderIntermediate(ContainerT* aContainer, if (!surface) { return; } + compositor->SetRenderTarget(surface); // pre-render all of the layers into our temporary RenderLayers(aContainer, aManager, RenderTargetPixel::FromUntyped(aClipRect)); diff --git a/gfx/layers/composite/TiledContentHost.cpp b/gfx/layers/composite/TiledContentHost.cpp index 0d7cdb400dd..1cff1ea1e3d 100644 --- a/gfx/layers/composite/TiledContentHost.cpp +++ b/gfx/layers/composite/TiledContentHost.cpp @@ -461,15 +461,6 @@ TiledContentHost::RenderTile(const TileHost& aTile, return; } - nsIntRect screenBounds = aScreenRegion.GetBounds(); - Rect layerQuad(screenBounds.x, screenBounds.y, screenBounds.width, screenBounds.height); - RenderTargetRect quad = RenderTargetRect::FromUnknown(aTransform.TransformBounds(layerQuad)); - - if (!quad.Intersects(mCompositor->ClipRectInLayersCoordinates(mLayer, - RenderTargetIntRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height)))) { - return; - } - if (aBackgroundColor) { aEffectChain.mPrimaryEffect = new EffectSolidColor(ToColor(*aBackgroundColor)); nsIntRegionRectIterator it(aScreenRegion); From ba4692a8b66c4741ecaf3ffd18bb71ec72b71110 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Wed, 22 Oct 2014 14:26:17 +1300 Subject: [PATCH 017/153] Bug 1084672. Call NotifyDidPaint from the refresh driver to ensure it gets called regardless of whether OMTC is used or not. r=mattwoodrow --HG-- extra : rebase_source : 51cc908538bd127c0af9089ae92aa82146032015 --- layout/base/nsPresShell.cpp | 4 ---- layout/base/nsRefreshDriver.cpp | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index edc80a1f473..1154e465533 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -8803,10 +8803,6 @@ PresShell::DidPaintWindow() // about compositing of popups. return; } - - if (nsContentUtils::XPConnect()) { - nsContentUtils::XPConnect()->NotifyDidPaint(); - } } bool diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp index eec500d89dc..5de68a374f4 100644 --- a/layout/base/nsRefreshDriver.cpp +++ b/layout/base/nsRefreshDriver.cpp @@ -1362,6 +1362,10 @@ nsRefreshDriver::Tick(int64_t aNowEpoch, TimeStamp aNowTime) TRACING_INTERVAL_END); } profiler_tracing("Paint", "DisplayList", TRACING_INTERVAL_END); + + if (nsContentUtils::XPConnect()) { + nsContentUtils::XPConnect()->NotifyDidPaint(); + } } for (uint32_t i = 0; i < mPostRefreshObservers.Length(); ++i) { From 543509b815c93a68c03c00df92f719f497b2086c Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 4 Nov 2014 14:19:14 +1300 Subject: [PATCH 018/153] Bug 1093399. Fire UpdateReadyStateForData every time we get new data. r=cpearce --HG-- extra : rebase_source : e243ecb8a72615cdf697228f30e631144cde129d --- dom/media/MediaDecoder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/dom/media/MediaDecoder.cpp b/dom/media/MediaDecoder.cpp index e768ff9bde4..bdb28990768 100644 --- a/dom/media/MediaDecoder.cpp +++ b/dom/media/MediaDecoder.cpp @@ -1474,6 +1474,7 @@ void MediaDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int6 if (mDecoderStateMachine) { mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset); } + UpdateReadyStateForData(); } void MediaDecoder::UpdatePlaybackPosition(int64_t aTime) From 1ba600aa1f0c8ef6ccf1d618427aad078ce9aaeb Mon Sep 17 00:00:00 2001 From: Eric Faust Date: Thu, 30 Oct 2014 17:27:01 -0700 Subject: [PATCH 019/153] Bug 611388 - Part 0: Sanify how const is handled by Reflect.parse. (r=shu) --- js/src/jsreflect.cpp | 59 ++++++++++++++++++-------------------------- js/src/jsreflect.h | 1 - 2 files changed, 24 insertions(+), 36 deletions(-) diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index d897df76ea9..1208bc976f8 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -1733,7 +1733,7 @@ class ASTSerializer bool declaration(ParseNode *pn, MutableHandleValue dst); bool variableDeclaration(ParseNode *pn, bool let, MutableHandleValue dst); - bool variableDeclarator(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst); + bool variableDeclarator(ParseNode *pn, MutableHandleValue dst); bool let(ParseNode *pn, bool expr, MutableHandleValue dst); bool importDeclaration(ParseNode *pn, MutableHandleValue dst); bool importSpecifier(ParseNode *pn, MutableHandleValue dst); @@ -1785,9 +1785,9 @@ class ASTSerializer bool identifier(ParseNode *pn, MutableHandleValue dst); bool literal(ParseNode *pn, MutableHandleValue dst); - bool pattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst); - bool arrayPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst); - bool objectPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst); + bool pattern(ParseNode *pn, MutableHandleValue dst); + bool arrayPattern(ParseNode *pn, MutableHandleValue dst); + bool objectPattern(ParseNode *pn, MutableHandleValue dst); bool function(ParseNode *pn, ASTType type, MutableHandleValue dst); bool functionArgsAndBody(ParseNode *pn, NodeVector &args, NodeVector &defaults, @@ -2023,15 +2023,14 @@ ASTSerializer::variableDeclaration(ParseNode *pn, bool let, MutableHandleValue d { MOZ_ASSERT(let ? pn->isKind(PNK_LET) : (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST))); - /* Later updated to VARDECL_CONST if we find a PND_CONST declarator. */ - VarDeclKind kind = let ? VARDECL_LET : VARDECL_VAR; + VarDeclKind kind = let ? VARDECL_LET : pn->isKind(PNK_VAR) ? VARDECL_VAR : VARDECL_CONST; NodeVector dtors(cx); if (!dtors.reserve(pn->pn_count)) return false; for (ParseNode *next = pn->pn_head; next; next = next->pn_next) { RootedValue child(cx); - if (!variableDeclarator(next, &kind, &child)) + if (!variableDeclarator(next, &child)) return false; dtors.infallibleAppend(child); } @@ -2039,7 +2038,7 @@ ASTSerializer::variableDeclaration(ParseNode *pn, bool let, MutableHandleValue d } bool -ASTSerializer::variableDeclarator(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst) +ASTSerializer::variableDeclarator(ParseNode *pn, MutableHandleValue dst) { ParseNode *pnleft; ParseNode *pnright; @@ -2060,7 +2059,7 @@ ASTSerializer::variableDeclarator(ParseNode *pn, VarDeclKind *pkind, MutableHand } RootedValue left(cx), right(cx); - return pattern(pnleft, pkind, &left) && + return pattern(pnleft, &left) && optExpression(pnright, &right) && builder.variableDeclarator(left, right, &pn->pn_pos, dst); } @@ -2081,15 +2080,10 @@ ASTSerializer::let(ParseNode *pn, bool expr, MutableHandleValue dst) if (!dtors.reserve(letHead->pn_count)) return false; - VarDeclKind kind = VARDECL_LET_HEAD; - for (ParseNode *next = letHead->pn_head; next; next = next->pn_next) { RootedValue child(cx); - /* - * Unlike in |variableDeclaration|, this does not update |kind|; since let-heads do - * not contain const declarations, declarators should never have PND_CONST set. - */ - if (!variableDeclarator(next, &kind, &child)) + + if (!variableDeclarator(next, &child)) return false; dtors.infallibleAppend(child); } @@ -2260,7 +2254,7 @@ ASTSerializer::catchClause(ParseNode *pn, bool *isGuarded, MutableHandleValue ds RootedValue var(cx), guard(cx), body(cx); - if (!pattern(pn->pn_kid1, nullptr, &var) || + if (!pattern(pn->pn_kid1, &var) || !optExpression(pn->pn_kid2, &guard)) { return false; } @@ -2448,7 +2442,7 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst) if (head->isKind(PNK_FORIN)) { RootedValue var(cx); return (!head->pn_kid1 - ? pattern(head->pn_kid2, nullptr, &var) + ? pattern(head->pn_kid2, &var) : head->pn_kid1->isKind(PNK_LEXICALSCOPE) ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) : variableDeclaration(head->pn_kid1, false, &var)) && @@ -2458,7 +2452,7 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst) if (head->isKind(PNK_FOROF)) { RootedValue var(cx); return (!head->pn_kid1 - ? pattern(head->pn_kid2, nullptr, &var) + ? pattern(head->pn_kid2, &var) : head->pn_kid1->isKind(PNK_LEXICALSCOPE) ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) : variableDeclaration(head->pn_kid1, false, &var)) && @@ -2591,7 +2585,7 @@ ASTSerializer::comprehensionBlock(ParseNode *pn, MutableHandleValue dst) bool isForOf = in->isKind(PNK_FOROF); RootedValue patt(cx), src(cx); - return pattern(in->pn_kid2, nullptr, &patt) && + return pattern(in->pn_kid2, &patt) && expression(in->pn_kid3, &src) && builder.comprehensionBlock(patt, src, isForEach, isForOf, &in->pn_pos, dst); } @@ -2762,7 +2756,7 @@ ASTSerializer::expression(ParseNode *pn, MutableHandleValue dst) LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT); RootedValue lhs(cx), rhs(cx); - return pattern(pn->pn_left, nullptr, &lhs) && + return pattern(pn->pn_left, &lhs) && expression(pn->pn_right, &rhs) && builder.assignmentExpression(op, lhs, rhs, &pn->pn_pos, dst); } @@ -3122,7 +3116,7 @@ ASTSerializer::literal(ParseNode *pn, MutableHandleValue dst) } bool -ASTSerializer::arrayPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst) +ASTSerializer::arrayPattern(ParseNode *pn, MutableHandleValue dst) { MOZ_ASSERT(pn->isKind(PNK_ARRAY)); @@ -3136,14 +3130,14 @@ ASTSerializer::arrayPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValu } else if (next->isKind(PNK_SPREAD)) { RootedValue target(cx); RootedValue spread(cx); - if (!pattern(next->pn_kid, pkind, &target)) + if (!pattern(next->pn_kid, &target)) return false; if(!builder.spreadExpression(target, &next->pn_pos, &spread)) return false; elts.infallibleAppend(spread); } else { RootedValue patt(cx); - if (!pattern(next, pkind, &patt)) + if (!pattern(next, &patt)) return false; elts.infallibleAppend(patt); } @@ -3153,7 +3147,7 @@ ASTSerializer::arrayPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValu } bool -ASTSerializer::objectPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst) +ASTSerializer::objectPattern(ParseNode *pn, MutableHandleValue dst) { MOZ_ASSERT(pn->isKind(PNK_OBJECT)); @@ -3178,7 +3172,7 @@ ASTSerializer::objectPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleVal } RootedValue patt(cx), prop(cx); - if (!pattern(target, pkind, &patt) || + if (!pattern(target, &patt) || !builder.propertyPattern(key, patt, propdef->isKind(PNK_SHORTHAND), &propdef->pn_pos, &prop)) { @@ -3192,20 +3186,15 @@ ASTSerializer::objectPattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleVal } bool -ASTSerializer::pattern(ParseNode *pn, VarDeclKind *pkind, MutableHandleValue dst) +ASTSerializer::pattern(ParseNode *pn, MutableHandleValue dst) { JS_CHECK_RECURSION(cx, return false); switch (pn->getKind()) { case PNK_OBJECT: - return objectPattern(pn, pkind, dst); + return objectPattern(pn, dst); case PNK_ARRAY: - return arrayPattern(pn, pkind, dst); - - case PNK_NAME: - if (pkind && (pn->pn_dflags & PND_CONST)) - *pkind = VARDECL_CONST; - /* FALL THROUGH */ + return arrayPattern(pn, dst); default: return expression(pn, dst); @@ -3348,7 +3337,7 @@ ASTSerializer::functionArgs(ParseNode *pn, ParseNode *pnargs, ParseNode *pndestr */ while ((arg && arg != pnbody) || destruct) { if (destruct && destruct->pn_right->frameSlot() == i) { - if (!pattern(destruct->pn_left, nullptr, &node) || !args.append(node)) + if (!pattern(destruct->pn_left, &node) || !args.append(node)) return false; destruct = destruct->pn_next; } else if (arg && arg != pnbody) { diff --git a/js/src/jsreflect.h b/js/src/jsreflect.h index aed8b12408e..a592bfe0cb2 100644 --- a/js/src/jsreflect.h +++ b/js/src/jsreflect.h @@ -73,7 +73,6 @@ enum VarDeclKind { VARDECL_VAR = 0, VARDECL_CONST, VARDECL_LET, - VARDECL_LET_HEAD, VARDECL_LIMIT }; From 3859ec66a31419e09a9a0ce6d785ff3b12da4d57 Mon Sep 17 00:00:00 2001 From: Eric Faust Date: Thu, 30 Oct 2014 17:27:03 -0700 Subject: [PATCH 020/153] Bug 611388 - |const| should be block scoped and require an initializer. (r=shu) --- b2g/chrome/content/identity.js | 16 +- .../tests/chrome/templates_shared.js | 23 +- js/src/frontend/BytecodeEmitter.cpp | 20 +- js/src/frontend/FullParseHandler.h | 4 +- js/src/frontend/ParseNode.cpp | 4 +- js/src/frontend/ParseNode.h | 16 +- js/src/frontend/Parser.cpp | 219 ++++++++++++------ js/src/frontend/Parser.h | 13 +- js/src/jit-test/tests/asm.js/testGlobals.js | 1 - .../jit-test/tests/auto-regress/bug487570.js | 2 +- .../jit-test/tests/auto-regress/bug495843.js | 2 +- js/src/jit-test/tests/basic/bug639797.js | 2 +- .../tests/basic/functionRedeclConst.js | 5 + .../tests/basic/functionRedeclGlobalConst.js | 3 + .../jit-test/tests/basic/functionRedeclLet.js | 5 + js/src/jit-test/tests/basic/letTDZDelete.js | 13 +- .../jit-test/tests/basic/letTDZEffectful.js | 13 +- .../basic/syntax-error-illegal-character.js | 8 +- .../basic/testDestructuringVarInsideWith.js | 2 +- .../tests/jaeger/recompile/bug641269.js | 2 + js/src/js.msg | 7 +- js/src/jsreflect.cpp | 26 ++- js/src/jsscript.cpp | 4 +- js/src/jsscript.h | 5 + .../ecma_5/Object/freeze-global-eval-const.js | 2 +- .../tests/js1_5/Regress/regress-360969-03.js | 2 +- .../tests/js1_5/Regress/regress-360969-04.js | 2 +- .../tests/js1_5/extensions/regress-452565.js | 2 +- js/src/tests/js1_6/Regress/regress-372565.js | 4 +- js/src/tests/js1_7/block/regress-349507.js | 3 +- js/src/tests/js1_8/genexps/regress-384991.js | 2 +- .../js1_8_1/regress/regress-452498-068.js | 2 +- .../js1_8_1/regress/regress-452498-092.js | 2 +- .../js1_8_1/regress/regress-452498-101.js | 2 +- .../js1_8_1/regress/regress-452498-102.js | 4 +- .../js1_8_1/regress/regress-452498-112.js | 4 +- .../js1_8_1/regress/regress-452498-117.js | 6 +- .../js1_8_1/regress/regress-452498-160.js | 2 +- .../js1_8_1/regress/regress-452498-185.js | 2 +- .../js1_8_1/regress/regress-452498-187.js | 2 +- .../js1_8_1/regress/regress-452498-192.js | 3 +- js/src/tests/js1_8_1/strict/12.2.1.js | 8 +- .../redeclaration-of-catch-warning.js | 2 +- .../tests/js1_8_5/extensions/reflect-parse.js | 12 +- js/src/vm/ScopeObject.cpp | 30 ++- js/src/vm/ScopeObject.h | 2 +- js/src/vm/Xdr.h | 2 +- .../modules/osfile_shared_allthreads.jsm | 10 +- .../components/places/tests/head_common.js | 5 +- 49 files changed, 320 insertions(+), 212 deletions(-) create mode 100644 js/src/jit-test/tests/basic/functionRedeclConst.js create mode 100644 js/src/jit-test/tests/basic/functionRedeclGlobalConst.js create mode 100644 js/src/jit-test/tests/basic/functionRedeclLet.js diff --git a/b2g/chrome/content/identity.js b/b2g/chrome/content/identity.js index 1d87844ad4d..548ecf54d8f 100644 --- a/b2g/chrome/content/identity.js +++ b/b2g/chrome/content/identity.js @@ -31,15 +31,15 @@ function log(...aMessageArgs) { log("\n\n======================= identity.js =======================\n\n"); // This script may be injected more than once into an iframe. -// Ensure we don't redefine contstants +// It's hard to do this with |const| like we should, so use var instead. if (typeof kIdentityJSLoaded === 'undefined') { - const kIdentityDelegateWatch = "identity-delegate-watch"; - const kIdentityDelegateRequest = "identity-delegate-request"; - const kIdentityDelegateLogout = "identity-delegate-logout"; - const kIdentityDelegateReady = "identity-delegate-ready"; - const kIdentityDelegateFinished = "identity-delegate-finished"; - const kIdentityControllerDoMethod = "identity-controller-doMethod"; - const kIdentktyJSLoaded = true; + var kIdentityDelegateWatch = "identity-delegate-watch"; + var kIdentityDelegateRequest = "identity-delegate-request"; + var kIdentityDelegateLogout = "identity-delegate-logout"; + var kIdentityDelegateReady = "identity-delegate-ready"; + var kIdentityDelegateFinished = "identity-delegate-finished"; + var kIdentityControllerDoMethod = "identity-controller-doMethod"; + var kIdentktyJSLoaded = true; } var showUI = false; diff --git a/dom/xul/templates/tests/chrome/templates_shared.js b/dom/xul/templates/tests/chrome/templates_shared.js index 93b13d316af..a73e602424d 100644 --- a/dom/xul/templates/tests/chrome/templates_shared.js +++ b/dom/xul/templates/tests/chrome/templates_shared.js @@ -54,12 +54,23 @@ const debug = false; var expectedConsoleMessages = []; var expectLoggedMessages = null; -try { - const RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"]. - getService(Components.interfaces.nsIRDFService); - const ContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"]. - getService(Components.interfaces.nsIRDFContainerUtils); -} catch(ex) { } +function get_RDF() { + try { + return Components.classes["@mozilla.org/rdf/rdf-service;1"]. + getService(Components.interfaces.nsIRDFService); + } catch (ex) { } +} + +function get_ContainerUtils() +{ + try { + return Components.classes["@mozilla.org/rdf/container-utils;1"]. + getService(Components.interfaces.nsIRDFContainerUtils); + } catch(ex) { } +} + +const RDF = get_RDF(); +const ContainerUtils = get_ContainerUtils(); var xmlDoc; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index e79883ec966..645e31e89ab 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1240,7 +1240,7 @@ LookupAliasedName(BytecodeEmitter *bce, HandleScript script, PropertyName *name, if (freeVariables[i].isHoistedUse() && bindingIndex >= lexicalBegin) { MOZ_ASSERT(pn); MOZ_ASSERT(pn->isUsed()); - pn->pn_dflags |= PND_LET; + pn->pn_dflags |= PND_LEXICAL; } break; @@ -1289,7 +1289,7 @@ AssignHops(BytecodeEmitter *bce, ParseNode *pn, unsigned src, ScopeCoordinate *d static inline MaybeCheckLexical NodeNeedsCheckLexical(ParseNode *pn) { - return pn->isHoistedLetUse() ? CheckLexical : DontCheckLexical; + return pn->isHoistedLexicalUse() ? CheckLexical : DontCheckLexical; } static bool @@ -1461,6 +1461,7 @@ BytecodeEmitter::isAliasedName(ParseNode *pn) switch (dn->kind()) { case Definition::LET: + case Definition::CONST: /* * There are two ways to alias a let variable: nested functions and * dynamic scope operations. (This is overly conservative since the @@ -1485,7 +1486,7 @@ BytecodeEmitter::isAliasedName(ParseNode *pn) */ return script->formalIsAliased(pn->pn_cookie.slot()); case Definition::VAR: - case Definition::CONST: + case Definition::GLOBALCONST: MOZ_ASSERT_IF(sc->allLocalsAliased(), script->varIsAliased(pn->pn_cookie.slot())); return script->varIsAliased(pn->pn_cookie.slot()); case Definition::PLACEHOLDER: @@ -1777,6 +1778,7 @@ BindNameToSlotHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) break; case Definition::VAR: + case Definition::GLOBALCONST: case Definition::CONST: case Definition::LET: switch (op) { @@ -2077,7 +2079,7 @@ CheckSideEffects(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool } } - if (pn->isHoistedLetUse()) { + if (pn->isHoistedLexicalUse()) { // Hoisted uses of lexical bindings throw on access. *answer = true; } @@ -4663,7 +4665,7 @@ EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) ParseNode *varList = pnLet->pn_left; MOZ_ASSERT(varList->isArity(PN_LIST)); ParseNode *letBody = pnLet->pn_right; - MOZ_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE)); + MOZ_ASSERT(letBody->isLexical() && letBody->isKind(PNK_LEXICALSCOPE)); int letHeadDepth = bce->stackDepth; @@ -4753,7 +4755,7 @@ static bool EmitForInOrOfVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool *letDecl) { *letDecl = pn->isKind(PNK_LEXICALSCOPE); - MOZ_ASSERT_IF(*letDecl, pn->isLet()); + MOZ_ASSERT_IF(*letDecl, pn->isLexical()); // If the left part is 'var x', emit code to define x if necessary using a // prolog opcode, but do not emit a pop. If it is 'let x', EnterBlockScope @@ -6706,7 +6708,7 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) // Assign the destructuring arguments before defining any functions, // see bug 419662. MOZ_ASSERT(pnchild->isKind(PNK_SEMI)); - MOZ_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_CONST)); + MOZ_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_GLOBALCONST)); if (!EmitTree(cx, bce, pnchild)) return false; pnchild = pnchild->pn_next; @@ -6839,7 +6841,7 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) break; case PNK_VAR: - case PNK_CONST: + case PNK_GLOBALCONST: if (!EmitVariables(cx, bce, pn, InitializeVars)) return false; break; @@ -7002,6 +7004,8 @@ frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) break; case PNK_LET: + case PNK_CONST: + MOZ_ASSERT_IF(pn->isKind(PNK_CONST), !pn->isArity(PN_BINARY)); ok = pn->isArity(PN_BINARY) ? EmitLet(cx, bce, pn) : EmitVariables(cx, bce, pn, InitializeVars); diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index a86dcff80a9..9ed975ddcef 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -667,8 +667,8 @@ class FullParseHandler uint16_t firstDominatingLexicalSlot) { MOZ_ASSERT(pn->isUsed()); - if (dn->isLet() && dn->pn_cookie.slot() < firstDominatingLexicalSlot) - pn->pn_dflags |= PND_LET; + if (dn->isLexical() && dn->pn_cookie.slot() < firstDominatingLexicalSlot) + pn->pn_dflags |= PND_LEXICAL; } static uintptr_t definitionToBits(Definition *dn) { diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index a51bc7b7af4..8327cc2f03f 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -308,7 +308,7 @@ const char * Definition::kindString(Kind kind) { static const char * const table[] = { - "", js_var_str, js_const_str, js_let_str, js_function_str, "argument", "unknown" + "", js_var_str, js_const_str, js_const_str, js_let_str, "argument", js_function_str, "unknown" }; MOZ_ASSERT(unsigned(kind) <= unsigned(ARG)); @@ -505,7 +505,7 @@ Parser::cloneLeftHandSide(ParseNode *opn) if (opn->isDefn()) { /* We copied some definition-specific state into pn. Clear it out. */ pn->pn_cookie.makeFree(); - pn->pn_dflags &= ~(PND_LET | PND_BOUND); + pn->pn_dflags &= ~(PND_LEXICAL | PND_BOUND); pn->setDefn(false); handler.linkUseToDef(pn, (Definition *) opn); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 2a4d7392229..4ef509ec9bf 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -114,6 +114,7 @@ class UpvarCookie F(CONTINUE) \ F(VAR) \ F(CONST) \ + F(GLOBALCONST) \ F(WITH) \ F(RETURN) \ F(NEW) \ @@ -682,7 +683,8 @@ class ParseNode Definition *resolve(); /* PN_CODE and PN_NAME pn_dflags bits. */ -#define PND_LET 0x01 /* let (block-scoped) binding or use of a hoisted let */ +#define PND_LEXICAL 0x01 /* lexical (block-scoped) binding or use of a hoisted + let or const */ #define PND_CONST 0x02 /* const binding (orthogonal to let) */ #define PND_ASSIGNED 0x04 /* set if ever LHS of assignment */ #define PND_PLACEHOLDER 0x08 /* placeholder definition for lexdep */ @@ -764,7 +766,7 @@ class ParseNode inline bool test(unsigned flag) const; - bool isLet() const { return test(PND_LET) && !isUsed(); } + bool isLexical() const { return test(PND_LEXICAL) && !isUsed(); } bool isConst() const { return test(PND_CONST); } bool isPlaceholder() const { return test(PND_PLACEHOLDER); } bool isDeoptimized() const { return test(PND_DEOPTIMIZED); } @@ -772,7 +774,7 @@ class ParseNode bool isClosed() const { return test(PND_CLOSED); } bool isBound() const { return test(PND_BOUND); } bool isImplicitArguments() const { return test(PND_IMPLICITARGUMENTS); } - bool isHoistedLetUse() const { return test(PND_LET) && isUsed(); } + bool isHoistedLexicalUse() const { return test(PND_LEXICAL) && isUsed(); } /* True if pn is a parsenode representing a literal constant. */ bool isLiteral() const { @@ -1414,7 +1416,7 @@ struct Definition : public ParseNode return pn_cookie.isFree(); } - enum Kind { MISSING = 0, VAR, CONST, LET, ARG, NAMED_LAMBDA, PLACEHOLDER }; + enum Kind { MISSING = 0, VAR, GLOBALCONST, CONST, LET, ARG, NAMED_LAMBDA, PLACEHOLDER }; bool canHaveInitializer() { return int(kind()) <= int(ARG); } @@ -1433,10 +1435,10 @@ struct Definition : public ParseNode return PLACEHOLDER; if (isOp(JSOP_GETARG)) return ARG; + if (isLexical()) + return isConst() ? CONST : LET; if (isConst()) - return CONST; - if (isLet()) - return LET; + return GLOBALCONST; return VAR; } }; diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index cc435cbe7e6..819ac036d42 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -122,7 +122,7 @@ MarkUsesAsHoistedLexical(ParseNode *pn) // Distinguish hoisted uses as a different JSOp for easier compilation. while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) { MOZ_ASSERT(pnu->isUsed()); - pnu->pn_dflags |= PND_LET; + pnu->pn_dflags |= PND_LEXICAL; pnup = &pnu->pn_link; } } @@ -137,7 +137,7 @@ ParseContext::define(TokenStream &ts, MOZ_ASSERT_IF(pn->isDefn(), pn->isPlaceholder()); Definition *prevDef = nullptr; - if (kind == Definition::LET) + if (kind == Definition::LET || kind == Definition::CONST) prevDef = decls_.lookupFirst(name); else MOZ_ASSERT(!decls_.lookupFirst(name)); @@ -148,7 +148,8 @@ ParseContext::define(TokenStream &ts, if (prevDef) { ParseNode **pnup = &prevDef->dn_uses; ParseNode *pnu; - unsigned start = (kind == Definition::LET) ? pn->pn_blockid : bodyid; + unsigned start = (kind == Definition::LET || kind == Definition::CONST) ? pn->pn_blockid + : bodyid; while ((pnu = *pnup) != nullptr && pnu->pn_blockid >= start) { MOZ_ASSERT(pnu->pn_blockid >= bodyid); @@ -170,7 +171,7 @@ ParseContext::define(TokenStream &ts, pn->pn_dflags |= prevDef->pn_dflags & PND_CLOSED; } - MOZ_ASSERT_IF(kind != Definition::LET, !lexdeps->lookup(name)); + MOZ_ASSERT_IF(kind != Definition::LET && kind != Definition::CONST, !lexdeps->lookup(name)); pn->setDefn(true); pn->pn_dflags &= ~PND_PLACEHOLDER; if (kind == Definition::CONST) @@ -197,7 +198,7 @@ ParseContext::define(TokenStream &ts, return false; break; - case Definition::CONST: + case Definition::GLOBALCONST: case Definition::VAR: if (sc->isFunctionBox()) { dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL); @@ -215,8 +216,9 @@ ParseContext::define(TokenStream &ts, break; case Definition::LET: + case Definition::CONST: dn->setOp(JSOP_INITLEXICAL); - dn->pn_dflags |= (PND_LET | PND_BOUND); + dn->pn_dflags |= (PND_LEXICAL | PND_BOUND); MOZ_ASSERT(dn->pn_cookie.level() == staticLevel); /* see bindLet */ if (atBodyLevel()) { if (!bodyLevelLexicals_.append(dn)) @@ -314,7 +316,8 @@ template void ParseContext::popLetDecl(JSAtom *atom) { - MOZ_ASSERT(ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::LET); + MOZ_ASSERT(ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::LET || + ParseHandler::getDefinitionKind(decls_.lookupFirst(atom)) == Definition::CONST); decls_.remove(atom); } @@ -338,6 +341,7 @@ AppendPackedBindings(const ParseContext *pc, const DeclVector &vec kind = Binding::VARIABLE; break; case Definition::CONST: + case Definition::GLOBALCONST: kind = Binding::CONSTANT; break; case Definition::ARG: @@ -1169,6 +1173,7 @@ struct BindData JSOp op; /* prolog bytecode or nop */ Binder binder; /* binder, discriminates u */ + bool isConst; /* const binding? */ struct LetData { explicit LetData(ExclusiveContext *cx) : blockObj(cx) {} @@ -1177,18 +1182,21 @@ struct BindData unsigned overflow; } let; - void initLet(VarContext varContext, StaticBlockObject *blockObj, unsigned overflow) { + void initLexical(VarContext varContext, StaticBlockObject *blockObj, unsigned overflow, + bool isConst = false) { this->pn = ParseHandler::null(); this->op = JSOP_INITLEXICAL; - this->binder = Parser::bindLet; + this->isConst = isConst; + this->binder = Parser::bindLexical; this->let.varContext = varContext; this->let.blockObj = blockObj; this->let.overflow = overflow; } - void initVarOrConst(JSOp op) { + void initVarOrGlobalConst(JSOp op) { this->op = op; - this->binder = Parser::bindVarOrConst; + this->isConst = op == JSOP_DEFCONST; + this->binder = Parser::bindVarOrGlobalConst; } }; @@ -1289,7 +1297,7 @@ static bool IsNonDominatingInScopedSwitch(ParseContext *pc, HandleAtom name, Definition *dn) { - MOZ_ASSERT(dn->isLet()); + MOZ_ASSERT(dn->isLexical()); StmtInfoPC *stmt = LexicalLookup(pc, name, nullptr, (StmtInfoPC *)nullptr); if (stmt && stmt->type == STMT_SWITCH) return dn->pn_cookie.slot() < stmt->firstDominatingLexicalInCase; @@ -1298,9 +1306,9 @@ IsNonDominatingInScopedSwitch(ParseContext *pc, HandleAtom nam static void AssociateUsesWithOuterDefinition(ParseNode *pnu, Definition *dn, Definition *outer_dn, - bool markUsesAsLet) + bool markUsesAsLexical) { - uint32_t dflags = markUsesAsLet ? PND_LET : 0; + uint32_t dflags = markUsesAsLexical ? PND_LEXICAL : 0; while (true) { pnu->pn_lexdef = outer_dn; pnu->pn_dflags |= dflags; @@ -1412,10 +1420,10 @@ Parser::leaveFunction(ParseNode *fn, ParseContextisLet() && - (bodyLevelHoistedUse || - IsNonDominatingInScopedSwitch(outerpc, name, outer_dn)); - AssociateUsesWithOuterDefinition(pnu, dn, outer_dn, markUsesAsLet); + bool markUsesAsLexical = outer_dn->isLexical() && + (bodyLevelHoistedUse || + IsNonDominatingInScopedSwitch(outerpc, name, outer_dn)); + AssociateUsesWithOuterDefinition(pnu, dn, outer_dn, markUsesAsLexical); } outer_dn->pn_dflags |= dn->pn_dflags & ~PND_PLACEHOLDER; @@ -1764,7 +1772,8 @@ Parser::checkFunctionDefinition(HandlePropertyName funName, MOZ_ASSERT(!dn->isUsed()); MOZ_ASSERT(dn->isDefn()); - bool throwRedeclarationError = dn->kind() == Definition::CONST || + bool throwRedeclarationError = dn->kind() == Definition::GLOBALCONST || + dn->kind() == Definition::CONST || dn->kind() == Definition::LET; if (options().extraWarningsOption || throwRedeclarationError) { JSAutoByteString name; @@ -1993,7 +2002,10 @@ Parser::checkFunctionDefinition(HandlePropertyName funName, * function (thereby avoiding JSOP_DEFFUN and dynamic name lookup). */ if (DefinitionNode dn = pc->decls().lookupFirst(funName)) { - if (dn == Definition::CONST) { + if (dn == Definition::GLOBALCONST || + dn == Definition::CONST || + dn == Definition::LET) + { JSAutoByteString name; if (!AtomToPrintableString(context, funName, &name) || !report(ParseError, false, null(), JSMSG_REDECLARED_VAR, @@ -2847,7 +2859,7 @@ Parser::matchLabel(MutableHandle label) template bool -Parser::reportRedeclaration(Node pn, bool isConst, HandlePropertyName name) +Parser::reportRedeclaration(Node pn, Definition::Kind redeclKind, HandlePropertyName name) { JSAutoByteString printable; if (!AtomToPrintableString(context, name, &printable)) @@ -2857,14 +2869,18 @@ Parser::reportRedeclaration(Node pn, bool isConst, HandlePropertyN if (stmt && stmt->type == STMT_CATCH) { report(ParseError, false, pn, JSMSG_REDECLARED_CATCH_IDENTIFIER, printable.ptr()); } else { - report(ParseError, false, pn, JSMSG_REDECLARED_VAR, isConst ? "const" : "variable", - printable.ptr()); + if (redeclKind == Definition::ARG) { + report(ParseError, false, pn, JSMSG_REDECLARED_PARAM, printable.ptr()); + } else { + report(ParseError, false, pn, JSMSG_REDECLARED_VAR, Definition::kindString(redeclKind), + printable.ptr()); + } } return false; } /* - * Define a let-variable in a block, let-expression, or comprehension scope. pc + * Define a lexical binding in a block, let-expression, or comprehension scope. pc * must already be in such a scope. * * Throw a SyntaxError if 'atom' is an invalid name. Otherwise create a @@ -2874,8 +2890,8 @@ Parser::reportRedeclaration(Node pn, bool isConst, HandlePropertyN */ template <> /* static */ bool -Parser::bindLet(BindData *data, - HandlePropertyName name, Parser *parser) +Parser::bindLexical(BindData *data, + HandlePropertyName name, Parser *parser) { ParseContext *pc = parser->pc; ParseNode *pn = data->pn; @@ -2912,25 +2928,34 @@ Parser::bindLet(BindData *data, if (!pn->pn_cookie.set(parser->tokenStream, pc->staticLevel, index)) return false; + Definition *dn = pc->decls().lookupFirst(name); + Definition::Kind bindingKind = data->isConst ? Definition::CONST : Definition::LET; + /* * For bindings that are hoisted to the beginning of the block/function, * define() right now. Otherwise, delay define until PushLetScope. */ if (data->let.varContext == HoistVars) { - Definition *dn = pc->decls().lookupFirst(name); if (dn && dn->pn_blockid == pc->blockid()) - return parser->reportRedeclaration(pn, dn->isConst(), name); - if (!pc->define(parser->tokenStream, name, pn, Definition::LET)) + return parser->reportRedeclaration(pn, dn->kind(), name); + if (!pc->define(parser->tokenStream, name, pn, bindingKind)) return false; } if (blockObj) { bool redeclared; RootedId id(cx, NameToId(name)); - RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, index, &redeclared)); + RootedShape shape(cx, StaticBlockObject::addVar(cx, blockObj, id, + data->isConst, index, &redeclared)); if (!shape) { - if (redeclared) - parser->reportRedeclaration(pn, false, name); + if (redeclared) { + // The only way to be redeclared without a previous definition is if we're in a + // comma separated list in a DontHoistVars block, so a let block of for header. In + // that case, we must be redeclaring the same type of definition as we're trying to + // make. + Definition::Kind dnKind = dn ? dn->kind() : bindingKind; + parser->reportRedeclaration(pn, dnKind, name); + } return false; } @@ -2948,8 +2973,8 @@ Parser::bindLet(BindData *data, template <> /* static */ bool -Parser::bindLet(BindData *data, - HandlePropertyName name, Parser *parser) +Parser::bindLexical(BindData *data, + HandlePropertyName name, Parser *parser) { if (!parser->checkStrictBinding(name, data->pn)) return false; @@ -3091,8 +3116,8 @@ OuterLet(ParseContext *pc, StmtInfoPC *stmt, HandleAtom atom) template /* static */ bool -Parser::bindVarOrConst(BindData *data, - HandlePropertyName name, Parser *parser) +Parser::bindVarOrGlobalConst(BindData *data, + HandlePropertyName name, Parser *parser) { ExclusiveContext *cx = parser->context; ParseContext *pc = parser->pc; @@ -3132,7 +3157,7 @@ Parser::bindVarOrConst(BindData *data, if (defs.empty()) { return pc->define(parser->tokenStream, name, pn, - isConstDecl ? Definition::CONST : Definition::VAR); + isConstDecl ? Definition::GLOBALCONST : Definition::VAR); } /* @@ -3159,6 +3184,7 @@ Parser::bindVarOrConst(BindData *data, bool inCatchBody = (stmt && stmt->type == STMT_CATCH); bool error = (isConstDecl || dn_kind == Definition::CONST || + dn_kind == Definition::GLOBALCONST || (dn_kind == Definition::LET && (!inCatchBody || OuterLet(pc, stmt, name)))); @@ -3505,7 +3531,7 @@ Parser::pushLetScope(HandleStaticBlockObject blockObj, StmtInf if (!pn) return null(); - pn->pn_dflags |= PND_LET; + pn->pn_dflags |= PND_LEXICAL; /* Populate the new scope with decls found in the head with updated blockid. */ if (!ForEachLetDef(tokenStream, pc, blockObj, AddLetDecl(stmt->blockid))) @@ -3692,10 +3718,10 @@ Parser::variables(ParseNodeKind kind, bool *psimple, * The four options here are: * - PNK_VAR: We're parsing var declarations. * - PNK_CONST: We're parsing const declarations. + * - PNK_GLOBALCONST: We're parsing const declarations at toplevel (see bug 589119). * - PNK_LET: We are parsing a let declaration. - * - PNK_CALL: We are parsing the head of a let block. */ - MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_CALL); + MOZ_ASSERT(kind == PNK_VAR || kind == PNK_CONST || kind == PNK_LET || kind == PNK_GLOBALCONST); /* * The simple flag is set if the declaration has the form 'var x', with @@ -3703,7 +3729,11 @@ Parser::variables(ParseNodeKind kind, bool *psimple, */ MOZ_ASSERT_IF(psimple, *psimple); - JSOp op = kind == PNK_LET ? JSOP_NOP : kind == PNK_VAR ? JSOP_DEFVAR : JSOP_DEFCONST; + JSOp op = JSOP_NOP; + if (kind == PNK_VAR) + op = JSOP_DEFVAR; + else if (kind == PNK_GLOBALCONST) + op = JSOP_DEFCONST; Node pn = handler.newList(kind, null(), op); if (!pn) @@ -3715,10 +3745,12 @@ Parser::variables(ParseNodeKind kind, bool *psimple, * this code will change soon. */ BindData data(context); - if (kind == PNK_LET) - data.initLet(varContext, blockObj, JSMSG_TOO_MANY_LOCALS); - else - data.initVarOrConst(op); + if (kind == PNK_VAR || kind == PNK_GLOBALCONST) { + data.initVarOrGlobalConst(op); + } else { + data.initLexical(varContext, blockObj, JSMSG_TOO_MANY_LOCALS, + /* isConst = */ kind == PNK_CONST); + } bool first = true; Node pn2; @@ -3751,7 +3783,8 @@ Parser::variables(ParseNodeKind kind, bool *psimple, // See comment below for bindBeforeInitializer in the code that // handles the non-destructuring case. - bool bindBeforeInitializer = kind != PNK_LET || parsingForInOrOfInit; + bool bindBeforeInitializer = (kind != PNK_LET && kind != PNK_CONST) || + parsingForInOrOfInit; if (bindBeforeInitializer && !checkDestructuring(&data, pn2)) return null(); @@ -3788,10 +3821,10 @@ Parser::variables(ParseNodeKind kind, bool *psimple, } RootedPropertyName name(context, tokenStream.currentName()); - pn2 = newBindingNode(name, kind == PNK_VAR || kind == PNK_CONST, varContext); + pn2 = newBindingNode(name, kind == PNK_VAR || kind == PNK_GLOBALCONST, varContext); if (!pn2) return null(); - if (data.op == JSOP_DEFCONST) + if (data.isConst) handler.setFlag(pn2, PND_CONST); data.pn = pn2; @@ -3812,7 +3845,7 @@ Parser::variables(ParseNodeKind kind, bool *psimple, // If we are not parsing a let declaration, bind the name // now. Otherwise we must wait until after parsing the initializing // assignment. - bool bindBeforeInitializer = kind != PNK_LET; + bool bindBeforeInitializer = kind != PNK_LET && kind != PNK_CONST; if (bindBeforeInitializer && !data.binder(&data, name, this)) return null(); @@ -3826,6 +3859,11 @@ Parser::variables(ParseNodeKind kind, bool *psimple, if (!handler.finishInitializerAssignment(pn2, init, data.op)) return null(); } else { + if (data.isConst && !pc->parsingForInit) { + report(ParseError, false, null(), JSMSG_BAD_CONST_DECL); + return null(); + } + if (!data.binder(&data, name, this)) return null(); } @@ -3843,7 +3881,7 @@ Parser::variables(ParseNodeKind kind, bool *psimple, template <> ParseNode * -Parser::letDeclaration() +Parser::lexicalDeclaration(bool isConst) { handler.disableSyntaxParser(); @@ -3863,7 +3901,8 @@ Parser::letDeclaration() */ StmtInfoPC *stmt = pc->topStmt; if (stmt && (!stmt->maybeScope() || stmt->isForLetBlock)) { - report(ParseError, false, null(), JSMSG_LET_DECL_NOT_IN_BLOCK); + report(ParseError, false, null(), JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, + isConst ? "const" : "let"); return null(); } @@ -3882,9 +3921,10 @@ Parser::letDeclaration() * conflicting slots. Forbid top-level let declarations to * prevent such conflicts from ever occurring. */ - bool globalLet = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt; - if (options().selfHostingMode && globalLet) { - report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LET); + bool isGlobal = !pc->sc->isFunctionBox() && stmt == pc->topScopeStmt; + if (options().selfHostingMode && isGlobal) { + report(ParseError, false, null(), JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, + isConst ? "'const'" : "'let'"); return null(); } @@ -3902,7 +3942,12 @@ Parser::letDeclaration() * FIXME global-level lets are still considered vars until * other bugs are fixed. */ - pn = variables(globalLet ? PNK_VAR : PNK_LET); + ParseNodeKind kind = PNK_LET; + if (isGlobal) + kind = isConst ? PNK_GLOBALCONST : PNK_VAR; + else if (isConst) + kind = PNK_CONST; + pn = variables(kind); if (!pn) return null(); pn->pn_xflags |= PNX_POPVAR; @@ -3962,7 +4007,8 @@ Parser::letDeclaration() pc->blockNode = pn1; } - pn = variables(PNK_LET, nullptr, &pc->staticScope->as(), HoistVars); + pn = variables(isConst ? PNK_CONST : PNK_LET, nullptr, + &pc->staticScope->as(), HoistVars); if (!pn) return null(); pn->pn_xflags = PNX_POPVAR; @@ -3973,7 +4019,7 @@ Parser::letDeclaration() template <> SyntaxParseHandler::Node -Parser::letDeclaration() +Parser::lexicalDeclaration(bool) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; @@ -3994,7 +4040,7 @@ Parser::letStatement() pn = letBlock(LetStatement); MOZ_ASSERT_IF(pn, pn->isKind(PNK_LET) || pn->isKind(PNK_SEMI)); } else { - pn = letDeclaration(); + pn = lexicalDeclaration(/* isConst = */ false); } return pn; } @@ -4251,8 +4297,7 @@ Parser::exportDeclaration() break; case TOK_VAR: - case TOK_CONST: - kid = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); + kid = variables(PNK_VAR); if (!kid) return null(); kid->pn_xflags = PNX_POPVAR; @@ -4268,7 +4313,8 @@ Parser::exportDeclaration() // and fall through. tokenStream.ungetToken(); case TOK_LET: - kid = letDeclaration(); + case TOK_CONST: + kid = lexicalDeclaration(tt == TOK_CONST); if (!kid) return null(); break; @@ -4412,7 +4458,7 @@ Parser::isValidForStatementLHS(ParseNode *pn1, JSVersion versi if (isForDecl) { if (pn1->pn_count > 1) return false; - if (pn1->isOp(JSOP_DEFCONST)) + if (pn1->isKind(PNK_CONST)) return false; // In JS 1.7 only, for (var [K, V] in EXPR) has a special meaning. @@ -4450,6 +4496,22 @@ Parser::isValidForStatementLHS(ParseNode *pn1, JSVersion versi } } +template <> +bool +Parser::checkForHeadConstInitializers(ParseNode *pn1) +{ + if (!pn1->isKind(PNK_CONST)) + return true; + + for (ParseNode *assign = pn1->pn_head; assign; assign = assign->pn_next) { + MOZ_ASSERT(assign->isKind(PNK_ASSIGN) || assign->isKind(PNK_NAME)); + if (assign->isKind(PNK_NAME) && !assign->isAssigned()) + return false; + // PNK_ASSIGN nodes (destructuring assignment) are always assignments. + } + return true; +} + template <> ParseNode * Parser::forStatement() @@ -4509,11 +4571,11 @@ Parser::forStatement() * clause of an ordinary for loop. */ pc->parsingForInit = true; - if (tt == TOK_VAR || tt == TOK_CONST) { + if (tt == TOK_VAR) { isForDecl = true; tokenStream.consumeKnownToken(tt); - pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST); - } else if (tt == TOK_LET) { + pn1 = variables(PNK_VAR); + } else if (tt == TOK_LET || tt == TOK_CONST) { handler.disableSyntaxParser(); tokenStream.consumeKnownToken(tt); if (!tokenStream.peekToken(&tt)) @@ -4525,7 +4587,8 @@ Parser::forStatement() blockObj = StaticBlockObject::create(context); if (!blockObj) return null(); - pn1 = variables(PNK_LET, nullptr, blockObj, DontHoistVars); + pn1 = variables(tt == TOK_CONST ? PNK_CONST: PNK_LET, nullptr, blockObj, + DontHoistVars); } } else { pn1 = expr(); @@ -4717,8 +4780,14 @@ Parser::forStatement() if (blockObj) { /* * Desugar 'for (let A; B; C) D' into 'let (A) { for (; B; C) D }' - * to induce the correct scoping for A. + * to induce the correct scoping for A. Ensure here that the previously + * unchecked assignment mandate for const declarations holds. */ + if (!checkForHeadConstInitializers(pn1)) { + report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL); + return null(); + } + forLetImpliedBlock = pushLetScope(blockObj, &letStmt); if (!forLetImpliedBlock) return null(); @@ -4843,7 +4912,7 @@ Parser::forStatement() if (tt == TOK_VAR) { isForDecl = true; tokenStream.consumeKnownToken(tt); - lhsNode = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST, &simpleForDecl); + lhsNode = variables(PNK_VAR, &simpleForDecl); } else if (tt == TOK_CONST || tt == TOK_LET) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); @@ -5512,8 +5581,8 @@ Parser::tryStatement() * scoped, not a property of a new Object instance. This is * an intentional change that anticipates ECMA Ed. 4. */ - data.initLet(HoistVars, &pc->staticScope->template as(), - JSMSG_TOO_MANY_CATCH_VARS); + data.initLexical(HoistVars, &pc->staticScope->template as(), + JSMSG_TOO_MANY_CATCH_VARS); MOZ_ASSERT(data.let.blockObj); if (!tokenStream.getToken(&tt)) @@ -5640,9 +5709,10 @@ Parser::statement(bool canHaveDirectives) case TOK_CONST: if (!abortIfSyntaxParser()) return null(); - // FALL THROUGH + return lexicalDeclaration(/* isConst = */ true); + case TOK_VAR: { - Node pn = variables(tt == TOK_CONST ? PNK_CONST : PNK_VAR); + Node pn = variables(PNK_VAR); if (!pn) return null(); @@ -6619,7 +6689,8 @@ Parser::legacyComprehensionTail(ParseNode *bodyExpr, unsigned return null(); MOZ_ASSERT(pc->staticScope && pc->staticScope == pn->pn_objbox->object); - data.initLet(HoistVars, &pc->staticScope->as(), JSMSG_ARRAY_INIT_TOO_BIG); + data.initLexical(HoistVars, &pc->staticScope->as(), + JSMSG_ARRAY_INIT_TOO_BIG); while (true) { /* @@ -7062,7 +7133,7 @@ Parser::comprehensionFor(GeneratorKind comprehensionKind) RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); if (!blockObj) return null(); - data.initLet(DontHoistVars, blockObj, JSMSG_TOO_MANY_LOCALS); + data.initLexical(DontHoistVars, blockObj, JSMSG_TOO_MANY_LOCALS); Node lhs = newName(name); if (!lhs) return null(); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 06293e08f8d..c28f0f03607 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -542,7 +542,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter Node tryStatement(); Node debuggerStatement(); - Node letDeclaration(); + Node lexicalDeclaration(bool isConst); Node letStatement(); Node importDeclaration(); Node exportDeclaration(); @@ -634,6 +634,7 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter bool isValidForStatementLHS(Node pn1, JSVersion version, bool forDecl, bool forEach, ParseNodeKind headKind); + bool checkForHeadConstInitializers(Node pn1); bool checkAndMarkAsIncOperand(Node kid, TokenKind tt, bool preorder); bool checkStrictAssignment(Node lhs); bool checkStrictBinding(PropertyName *name, Node pn); @@ -667,16 +668,16 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter HandlePropertyName name, Parser *parser); static bool - bindLet(BindData *data, - HandlePropertyName name, Parser *parser); + bindLexical(BindData *data, + HandlePropertyName name, Parser *parser); static bool - bindVarOrConst(BindData *data, - HandlePropertyName name, Parser *parser); + bindVarOrGlobalConst(BindData *data, + HandlePropertyName name, Parser *parser); static Node null() { return ParseHandler::null(); } - bool reportRedeclaration(Node pn, bool isConst, HandlePropertyName name); + bool reportRedeclaration(Node pn, Definition::Kind redeclKind, HandlePropertyName name); bool reportBadReturn(Node pn, ParseReportKind kind, unsigned errnum, unsigned anonerrnum); DefinitionNode getOrCreateLexicalDependency(ParseContext *pc, JSAtom *atom); diff --git a/js/src/jit-test/tests/asm.js/testGlobals.js b/js/src/jit-test/tests/asm.js/testGlobals.js index a41c6fe95ab..3ceea961205 100644 --- a/js/src/jit-test/tests/asm.js/testGlobals.js +++ b/js/src/jit-test/tests/asm.js/testGlobals.js @@ -2,7 +2,6 @@ load(libdir + "asm.js"); load(libdir + "asserts.js"); assertAsmTypeFail(USE_ASM + "var i; function f(){} return f"); -assertAsmTypeFail(USE_ASM + "const i; function f(){} return f"); assertEq(asmLink(asmCompile(USE_ASM + "var i=0; function f(){} return f"))(), undefined); assertEq(asmLink(asmCompile(USE_ASM + "const i=0; function f(){} return f"))(), undefined); assertEq(asmLink(asmCompile(USE_ASM + "var i=42; function f(){ return i|0 } return f"))(), 42); diff --git a/js/src/jit-test/tests/auto-regress/bug487570.js b/js/src/jit-test/tests/auto-regress/bug487570.js index 9e4a7b34961..d8f0b09d176 100644 --- a/js/src/jit-test/tests/auto-regress/bug487570.js +++ b/js/src/jit-test/tests/auto-regress/bug487570.js @@ -9,5 +9,5 @@ for each (y in []) ) ) -{const functional} +{const functional=undefined} })() diff --git a/js/src/jit-test/tests/auto-regress/bug495843.js b/js/src/jit-test/tests/auto-regress/bug495843.js index 137e4bb2631..673e8294eca 100644 --- a/js/src/jit-test/tests/auto-regress/bug495843.js +++ b/js/src/jit-test/tests/auto-regress/bug495843.js @@ -1,5 +1,5 @@ // Binary: cache/js-dbg-64-fe91973cc783-linux // Flags: // -const x;[x]=''; +const [x]=''; for(;[] && false;){} diff --git a/js/src/jit-test/tests/basic/bug639797.js b/js/src/jit-test/tests/basic/bug639797.js index c1511ba1790..f061fac46d1 100644 --- a/js/src/jit-test/tests/basic/bug639797.js +++ b/js/src/jit-test/tests/basic/bug639797.js @@ -1 +1 @@ -Function("with([])const x=0")() +Function("with([]){const x=0}")() diff --git a/js/src/jit-test/tests/basic/functionRedeclConst.js b/js/src/jit-test/tests/basic/functionRedeclConst.js new file mode 100644 index 00000000000..8b0af654650 --- /dev/null +++ b/js/src/jit-test/tests/basic/functionRedeclConst.js @@ -0,0 +1,5 @@ +// |jit-test| error: TypeError +{ + const x = 0; + function x() { } +} diff --git a/js/src/jit-test/tests/basic/functionRedeclGlobalConst.js b/js/src/jit-test/tests/basic/functionRedeclGlobalConst.js new file mode 100644 index 00000000000..90784cc1802 --- /dev/null +++ b/js/src/jit-test/tests/basic/functionRedeclGlobalConst.js @@ -0,0 +1,3 @@ +// |jit-test| error: TypeError +const x = 0; +function x() { } diff --git a/js/src/jit-test/tests/basic/functionRedeclLet.js b/js/src/jit-test/tests/basic/functionRedeclLet.js new file mode 100644 index 00000000000..6ec6dfd499a --- /dev/null +++ b/js/src/jit-test/tests/basic/functionRedeclLet.js @@ -0,0 +1,5 @@ +// |jit-test| error: TypeError +{ + let x; + function x() { } +} diff --git a/js/src/jit-test/tests/basic/letTDZDelete.js b/js/src/jit-test/tests/basic/letTDZDelete.js index 6fb1048f0cb..966ee0ef1f6 100644 --- a/js/src/jit-test/tests/basic/letTDZDelete.js +++ b/js/src/jit-test/tests/basic/letTDZDelete.js @@ -9,15 +9,4 @@ function assertThrowsReferenceError(f) { } assertThrowsReferenceError(function () { delete x; let x; }); - -// FIXME do this unconditionally once bug 611388 lands. -function constIsLexical() { - try { - (function () { z++; const z; })(); - return false; - } catch (e) { - return true; - } -} -if (constIsLexical()) - assertThrowsReferenceError(function () { delete x; const x; }); +assertThrowsReferenceError(function () { delete x; const x = undefined; }); diff --git a/js/src/jit-test/tests/basic/letTDZEffectful.js b/js/src/jit-test/tests/basic/letTDZEffectful.js index e2f859bf1e4..62245d8c16b 100644 --- a/js/src/jit-test/tests/basic/letTDZEffectful.js +++ b/js/src/jit-test/tests/basic/letTDZEffectful.js @@ -10,15 +10,4 @@ function assertThrowsReferenceError(f) { // TDZ is effectful, don't optimize out x. assertThrowsReferenceError(function () { x; let x; }); - -// FIXME do this unconditionally once bug 611388 lands. -function constIsLexical() { - try { - (function () { z++; const z; })(); - return false; - } catch (e) { - return true; - } -} -if (constIsLexical()) - assertThrowsReferenceError(function () { x; const x; }); +assertThrowsReferenceError(function () { x; const x = undefined; }); diff --git a/js/src/jit-test/tests/basic/syntax-error-illegal-character.js b/js/src/jit-test/tests/basic/syntax-error-illegal-character.js index 3544308c912..1f186667f6d 100644 --- a/js/src/jit-test/tests/basic/syntax-error-illegal-character.js +++ b/js/src/jit-test/tests/basic/syntax-error-illegal-character.js @@ -235,10 +235,10 @@ test("const x = 1 @"); test("const x = 1 + @"); test("const x = 1 + 2 @"); test("const x = 1 + 2, @"); -test("const x = 1 + 2, y @"); -test("const x = 1 + 2, y, @"); -test("const x = 1 + 2, y, z @"); -test("const x = 1 + 2, y, z; @"); +test("const x = 1 + 2, y = 0@"); +test("const x = 1 + 2, y = 0, @"); +test("const x = 1 + 2, y = 0, z = 0 @"); +test("const x = 1 + 2, y = 0, z = 0; @"); test("const [ @"); test("const [ x @"); diff --git a/js/src/jit-test/tests/basic/testDestructuringVarInsideWith.js b/js/src/jit-test/tests/basic/testDestructuringVarInsideWith.js index 54cae4a2112..b233c9ceb2f 100644 --- a/js/src/jit-test/tests/basic/testDestructuringVarInsideWith.js +++ b/js/src/jit-test/tests/basic/testDestructuringVarInsideWith.js @@ -1,4 +1,4 @@ with ({b:1}) { const [ b ] = []; + assertEq(b, undefined); } -assertEq(b, undefined); diff --git a/js/src/jit-test/tests/jaeger/recompile/bug641269.js b/js/src/jit-test/tests/jaeger/recompile/bug641269.js index 64a2429efdd..7eca2e2d8c1 100644 --- a/js/src/jit-test/tests/jaeger/recompile/bug641269.js +++ b/js/src/jit-test/tests/jaeger/recompile/bug641269.js @@ -1,3 +1,5 @@ +// |jit-test| error: ReferenceError + var g = newGlobal(); var dbg = new g.Debugger(this); diff --git a/js/src/js.msg b/js/src/js.msg index 7e9845c9ac8..abb167d2740 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -110,7 +110,7 @@ MSG_DEF(JSMSG_BAD_PROTOTYPE, 1, JSEXN_TYPEERR, "'prototype' property o MSG_DEF(JSMSG_IN_NOT_OBJECT, 1, JSEXN_TYPEERR, "invalid 'in' operand {0}") MSG_DEF(JSMSG_TOO_MANY_CON_SPREADARGS, 0, JSEXN_RANGEERR, "too many constructor arguments") MSG_DEF(JSMSG_TOO_MANY_FUN_SPREADARGS, 0, JSEXN_RANGEERR, "too many function arguments") -MSG_DEF(JSMSG_UNINITIALIZED_LEXICAL, 1, JSEXN_REFERENCEERR, "can't access let declaration `{0}' before initialization") +MSG_DEF(JSMSG_UNINITIALIZED_LEXICAL, 1, JSEXN_REFERENCEERR, "can't access lexical declaration `{0}' before initialization") // Date MSG_DEF(JSMSG_INVALID_DATE, 0, JSEXN_RANGEERR, "invalid date") @@ -181,6 +181,7 @@ MSG_DEF(JSMSG_AS_AFTER_RESERVED_WORD, 1, JSEXN_SYNTAXERR, "missing keyword 'as' MSG_DEF(JSMSG_BAD_ANON_GENERATOR_RETURN, 0, JSEXN_TYPEERR, "anonymous generator function returns a value") MSG_DEF(JSMSG_BAD_ARROW_ARGS, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)") MSG_DEF(JSMSG_BAD_BINDING, 1, JSEXN_SYNTAXERR, "redefining {0} is deprecated") +MSG_DEF(JSMSG_BAD_CONST_DECL, 0, JSEXN_SYNTAXERR, "missing = in const declaration") MSG_DEF(JSMSG_BAD_CONTINUE, 0, JSEXN_SYNTAXERR, "continue must be inside loop") MSG_DEF(JSMSG_BAD_DESTRUCT_ASS, 0, JSEXN_REFERENCEERR, "invalid destructuring assignment operator") MSG_DEF(JSMSG_BAD_DESTRUCT_TARGET, 0, JSEXN_SYNTAXERR, "invalid destructuring target") @@ -244,7 +245,7 @@ MSG_DEF(JSMSG_INVALID_FOR_OF_INIT, 0, JSEXN_SYNTAXERR, "for-of loop variable MSG_DEF(JSMSG_IN_AFTER_FOR_NAME, 0, JSEXN_SYNTAXERR, "missing 'in' or 'of' after for") MSG_DEF(JSMSG_LABEL_NOT_FOUND, 0, JSEXN_SYNTAXERR, "label not found") MSG_DEF(JSMSG_LET_COMP_BINDING, 0, JSEXN_SYNTAXERR, "'let' is not a valid name for a comprehension variable") -MSG_DEF(JSMSG_LET_DECL_NOT_IN_BLOCK, 0, JSEXN_SYNTAXERR, "let declaration not directly within block") +MSG_DEF(JSMSG_LEXICAL_DECL_NOT_IN_BLOCK, 1, JSEXN_SYNTAXERR, "{0} declaration not directly within block") MSG_DEF(JSMSG_LINE_BREAK_AFTER_THROW, 0, JSEXN_SYNTAXERR, "no line break is allowed between 'throw' and its expression") MSG_DEF(JSMSG_MALFORMED_ESCAPE, 1, JSEXN_SYNTAXERR, "malformed {0} character escape sequence") MSG_DEF(JSMSG_MISSING_BINARY_DIGITS, 0, JSEXN_SYNTAXERR, "missing binary digits after '0b'") @@ -285,7 +286,7 @@ MSG_DEF(JSMSG_REDECLARED_CATCH_IDENTIFIER, 1, JSEXN_TYPEERR, "redeclaration of i MSG_DEF(JSMSG_REDECLARED_PARAM, 1, JSEXN_TYPEERR, "redeclaration of formal parameter {0}") MSG_DEF(JSMSG_RESERVED_ID, 1, JSEXN_SYNTAXERR, "{0} is a reserved identifier") MSG_DEF(JSMSG_REST_WITH_DEFAULT, 0, JSEXN_SYNTAXERR, "rest parameter may not have a default") -MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LET,0, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level 'let' declarations") +MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, 1, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level {0} declarations") MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups") MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND, 0, JSEXN_SYNTAXERR, "missing ; after for-loop condition") MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT, 0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer") diff --git a/js/src/jsreflect.cpp b/js/src/jsreflect.cpp index 1208bc976f8..0ad286e2290 100644 --- a/js/src/jsreflect.cpp +++ b/js/src/jsreflect.cpp @@ -1732,7 +1732,7 @@ class ASTSerializer bool sourceElement(ParseNode *pn, MutableHandleValue dst); bool declaration(ParseNode *pn, MutableHandleValue dst); - bool variableDeclaration(ParseNode *pn, bool let, MutableHandleValue dst); + bool variableDeclaration(ParseNode *pn, bool lexical, MutableHandleValue dst); bool variableDeclarator(ParseNode *pn, MutableHandleValue dst); bool let(ParseNode *pn, bool expr, MutableHandleValue dst); bool importDeclaration(ParseNode *pn, MutableHandleValue dst); @@ -2001,6 +2001,7 @@ ASTSerializer::declaration(ParseNode *pn, MutableHandleValue dst) { MOZ_ASSERT(pn->isKind(PNK_FUNCTION) || pn->isKind(PNK_VAR) || + pn->isKind(PNK_GLOBALCONST) || pn->isKind(PNK_LET) || pn->isKind(PNK_CONST)); @@ -2009,21 +2010,28 @@ ASTSerializer::declaration(ParseNode *pn, MutableHandleValue dst) return function(pn, AST_FUNC_DECL, dst); case PNK_VAR: - case PNK_CONST: + case PNK_GLOBALCONST: return variableDeclaration(pn, false, dst); default: - MOZ_ASSERT(pn->isKind(PNK_LET)); + MOZ_ASSERT(pn->isKind(PNK_LET) || pn->isKind(PNK_CONST)); return variableDeclaration(pn, true, dst); } } bool -ASTSerializer::variableDeclaration(ParseNode *pn, bool let, MutableHandleValue dst) +ASTSerializer::variableDeclaration(ParseNode *pn, bool lexical, MutableHandleValue dst) { - MOZ_ASSERT(let ? pn->isKind(PNK_LET) : (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST))); + MOZ_ASSERT_IF(lexical, pn->isKind(PNK_LET) || pn->isKind(PNK_CONST)); + MOZ_ASSERT_IF(!lexical, pn->isKind(PNK_VAR) || pn->isKind(PNK_GLOBALCONST)); - VarDeclKind kind = let ? VARDECL_LET : pn->isKind(PNK_VAR) ? VARDECL_VAR : VARDECL_CONST; + VarDeclKind kind = VARDECL_ERR; + // Treat both the toplevel const binding (secretly var-like) and the lexical const + // the same way + if (lexical) + kind = pn->isKind(PNK_LET) ? VARDECL_LET : VARDECL_CONST; + else + kind = pn->isKind(PNK_VAR) ? VARDECL_VAR : VARDECL_CONST; NodeVector dtors(cx); if (!dtors.reserve(pn->pn_count)) @@ -2166,6 +2174,7 @@ ASTSerializer::exportDeclaration(ParseNode *pn, MutableHandleValue dst) case PNK_VAR: case PNK_CONST: + case PNK_GLOBALCONST: case PNK_LET: if (!variableDeclaration(kid, kind == PNK_LET, &decl)) return false; @@ -2308,7 +2317,7 @@ ASTSerializer::forInit(ParseNode *pn, MutableHandleValue dst) return true; } - return (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)) + return (pn->isKind(PNK_VAR) || pn->isKind(PNK_GLOBALCONST)) ? variableDeclaration(pn, false, dst) : expression(pn, dst); } @@ -2341,10 +2350,11 @@ ASTSerializer::statement(ParseNode *pn, MutableHandleValue dst) switch (pn->getKind()) { case PNK_FUNCTION: case PNK_VAR: - case PNK_CONST: + case PNK_GLOBALCONST: return declaration(pn, dst); case PNK_LET: + case PNK_CONST: return pn->isArity(PN_BINARY) ? let(pn, false, dst) : declaration(pn, dst); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 2014bb0c225..733a8d68400 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -129,11 +129,11 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle if (bi->aliased()) { // Per ES6, lexical bindings cannot be accessed until // initialized. Remember the first aliased slot that is a - // body-level let, so that they may be initialized to sentinel + // body-level lexical, so that they may be initialized to sentinel // magic values. if (numBodyLevelLexicals > 0 && nslots < aliasedBodyLevelLexicalBegin && - bi->kind() == Binding::VARIABLE && + bi.isBodyLevelLexical() && bi.localIndex() >= numVars) { aliasedBodyLevelLexicalBegin = nslots; diff --git a/js/src/jsscript.h b/js/src/jsscript.h index a19e80697eb..5dc1f5c4253 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -1744,6 +1744,11 @@ class BindingIter MOZ_ASSERT(i_ >= bindings_->numArgs()); return i_ - bindings_->numArgs(); } + bool isBodyLevelLexical() const { + MOZ_ASSERT(!done()); + const Binding &binding = **this; + return binding.kind() != Binding::ARGUMENT; + } const Binding &operator*() const { MOZ_ASSERT(!done()); return bindings_->bindingArray()[i_]; } const Binding *operator->() const { MOZ_ASSERT(!done()); return &bindings_->bindingArray()[i_]; } diff --git a/js/src/tests/ecma_5/Object/freeze-global-eval-const.js b/js/src/tests/ecma_5/Object/freeze-global-eval-const.js index 0fb507c2d76..a43f5c51c9b 100644 --- a/js/src/tests/ecma_5/Object/freeze-global-eval-const.js +++ b/js/src/tests/ecma_5/Object/freeze-global-eval-const.js @@ -5,7 +5,7 @@ */ try { - evalcx("Object.freeze(this); eval('const q;')"); + evalcx("Object.freeze(this); eval('const q = undefined;')"); } catch (e) { assertEq(e.message, "({lazy:false}) is not extensible"); } diff --git a/js/src/tests/js1_5/Regress/regress-360969-03.js b/js/src/tests/js1_5/Regress/regress-360969-03.js index 8f5c0ece416..19f29362260 100644 --- a/js/src/tests/js1_5/Regress/regress-360969-03.js +++ b/js/src/tests/js1_5/Regress/regress-360969-03.js @@ -28,7 +28,7 @@ function test() for (var i = 0; i < limit; i++) { - eval('const pv' + i + ';'); + eval('const pv' + i + ' = undefined;'); } reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/Regress/regress-360969-04.js b/js/src/tests/js1_5/Regress/regress-360969-04.js index 110fea217e2..ea53e0ebaf8 100644 --- a/js/src/tests/js1_5/Regress/regress-360969-04.js +++ b/js/src/tests/js1_5/Regress/regress-360969-04.js @@ -21,7 +21,7 @@ var limit = 2 << 16; for (var i = 0; i < limit; i++) { - eval('const pv' + i + ';'); + eval('const pv' + i + ' = undefined;'); } reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_5/extensions/regress-452565.js b/js/src/tests/js1_5/extensions/regress-452565.js index a51f6016e99..9a68f2ae417 100644 --- a/js/src/tests/js1_5/extensions/regress-452565.js +++ b/js/src/tests/js1_5/extensions/regress-452565.js @@ -14,7 +14,7 @@ printStatus (summary); jit(true); -const c; (function() { for (var j=0;j<5;++j) { c = 1; } })(); +const c = undefined; (function() { for (var j=0;j<5;++j) { c = 1; } })(); jit(false); diff --git a/js/src/tests/js1_6/Regress/regress-372565.js b/js/src/tests/js1_6/Regress/regress-372565.js index 1e105620132..e5c0e1b07bf 100644 --- a/js/src/tests/js1_6/Regress/regress-372565.js +++ b/js/src/tests/js1_6/Regress/regress-372565.js @@ -19,8 +19,8 @@ function test() enterFunc ('test'); printBugNumber(BUGNUMBER); printStatus (summary); - - (function() { for each(x in y) { } const x; }); + + (function() { for each(x in y) { } const x = undefined; }); reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_7/block/regress-349507.js b/js/src/tests/js1_7/block/regress-349507.js index dba51204a6b..e0c4dc4daf9 100644 --- a/js/src/tests/js1_7/block/regress-349507.js +++ b/js/src/tests/js1_7/block/regress-349507.js @@ -20,7 +20,6 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = /TypeError: redeclaration of const b/; try { eval('(function() { let(x = 1) { const b = 2 }; let b = 3; })'); @@ -30,7 +29,7 @@ function test() actual = ex + ''; } - reportMatch(expect, actual, summary); + reportCompare(expect, actual, summary); exitFunc ('test'); } diff --git a/js/src/tests/js1_8/genexps/regress-384991.js b/js/src/tests/js1_8/genexps/regress-384991.js index 7dd1cef7be3..a34adfb3fe6 100644 --- a/js/src/tests/js1_8/genexps/regress-384991.js +++ b/js/src/tests/js1_8/genexps/regress-384991.js @@ -47,7 +47,7 @@ function test() try { actual = 'No Error'; - (function () { f(x = yield); const x; }); + (function () { f(x = yield); const x = undefined; }); } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-068.js b/js/src/tests/js1_8_1/regress/regress-452498-068.js index 5143fd38948..352ada492c6 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-068.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-068.js @@ -46,7 +46,7 @@ function test() // ===== for( - const NaN; + const NaN = undefined; this.__defineSetter__("x4", function(){}); (eval("", (p={})))) let ({} = (((x ))(function ([]) {})), x1) y; diff --git a/js/src/tests/js1_8_1/regress/regress-452498-092.js b/js/src/tests/js1_8_1/regress/regress-452498-092.js index 383c8360929..40347f29e59 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-092.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-092.js @@ -25,7 +25,7 @@ function test() expect = 'TypeError: redeclaration of formal parameter e'; try { - eval('(function (e) { var e; const e; });'); + eval('(function (e) { var e; const e = undefined; });'); } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-101.js b/js/src/tests/js1_8_1/regress/regress-452498-101.js index c8cea7feb3f..5bd15b2b74a 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-101.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-101.js @@ -22,7 +22,7 @@ function test() // ------- Comment #101 From Gary Kwong [:nth10sd] - uneval(function(){with({functional: []}){x5, y = this;const y }}); + uneval(function(){with({functional: []}){x5, y = this;const y = undefined }}); // Assertion failure: strcmp(rval, with_cookie) == 0, at ../jsopcode.cpp:2567 reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_8_1/regress/regress-452498-102.js b/js/src/tests/js1_8_1/regress/regress-452498-102.js index 2e5212d2eda..81e6ae5dbfc 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-102.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-102.js @@ -32,9 +32,9 @@ function test() function f() { "" + (function(){ for( ; [function(){}] ; x = 0) - with({x: ""}) + with({x: ""}) { const x = [] - }); + }}); } f(); diff --git a/js/src/tests/js1_8_1/regress/regress-452498-112.js b/js/src/tests/js1_8_1/regress/regress-452498-112.js index 0d274ab7eff..19aa63b10ec 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-112.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-112.js @@ -22,11 +22,11 @@ function test() // ------- Comment #112 From Jesse Ruderman - expect = 'TypeError: q is not a function'; + expect = 'ReferenceError: can\'t access lexical declaration `q\' before initialization'; try { - q = new Function("(function() { q(3); })(); const q;"); q(); + q = new Function("(function() { q(3); })(); const q = undefined;"); q(); } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-117.js b/js/src/tests/js1_8_1/regress/regress-452498-117.js index 6ede09562d8..b52a5f55465 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-117.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-117.js @@ -28,7 +28,7 @@ function test() try { - eval('x; function x(){}; const x;'); + eval('x; function x(){}; const x = undefined;'); } catch(ex) { @@ -46,7 +46,7 @@ function test() // ===== try { - (function(){(yield []) (function(){with({}){x} }); const x;})(); + (function(){(yield []) (function(){with({}){x} }); const x = undefined;})(); } catch(ex) { @@ -81,7 +81,7 @@ function test() // ===== try { - eval('(function(){{for(c in (function (){ for(x in (x1))window} )()) {const x;} }})();'); + eval('(function(){{for(c in (function (){ for(x in (x1))window} )()) {const x = undefined;} }})();'); } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-160.js b/js/src/tests/js1_8_1/regress/regress-452498-160.js index a42f4926450..6498a0b5aaa 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-160.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-160.js @@ -22,7 +22,7 @@ function test() printStatus (summary); // crash [@ js_Interpret] - (eval("(function(){ this.watch(\"x\", function () { new function ()y } ); const y });"))(); + (eval("(function(){ this.watch(\"x\", function () { new function ()y } ); const y = undefined });"))(); x = NaN; reportCompare(expect, actual, summary + ': 2'); diff --git a/js/src/tests/js1_8_1/regress/regress-452498-185.js b/js/src/tests/js1_8_1/regress/regress-452498-185.js index 72c0844aa5f..f8ca37f6f9b 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-185.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-185.js @@ -21,7 +21,7 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - expect = 'TypeError: redeclaration of variable e'; + expect = 'TypeError: redeclaration of var e'; try { eval('{ var e = 3; let e = ""; } print(typeof e);'); diff --git a/js/src/tests/js1_8_1/regress/regress-452498-187.js b/js/src/tests/js1_8_1/regress/regress-452498-187.js index 0f9a011d4d0..d1d0ffdf276 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-187.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-187.js @@ -24,7 +24,7 @@ function test() expect = 'SyntaxError: invalid for/in left-hand side'; try { - eval('const x; for (x in []);'); + eval('const x = undefined; for (x in []);'); } catch(ex) { diff --git a/js/src/tests/js1_8_1/regress/regress-452498-192.js b/js/src/tests/js1_8_1/regress/regress-452498-192.js index 32835b1122e..6f176788b1f 100644 --- a/js/src/tests/js1_8_1/regress/regress-452498-192.js +++ b/js/src/tests/js1_8_1/regress/regress-452498-192.js @@ -21,7 +21,8 @@ function test() printBugNumber(BUGNUMBER); printStatus (summary); - with({x: (x -= 0)}){([]); const x } + let x; + with({x: (x -= 0)}){([]); const x = undefined; } reportCompare(expect, actual, summary); diff --git a/js/src/tests/js1_8_1/strict/12.2.1.js b/js/src/tests/js1_8_1/strict/12.2.1.js index f57d7f4a50a..674c7a7c605 100644 --- a/js/src/tests/js1_8_1/strict/12.2.1.js +++ b/js/src/tests/js1_8_1/strict/12.2.1.js @@ -26,19 +26,19 @@ assertEq(testLenientAndStrict('let x,arguments;', parsesSuccessfully, parseRaisesException(SyntaxError)), true); -assertEq(testLenientAndStrict('const eval;', +assertEq(testLenientAndStrict('const eval = undefined;', parsesSuccessfully, parseRaisesException(SyntaxError)), true); -assertEq(testLenientAndStrict('const x,eval;', +assertEq(testLenientAndStrict('const x = undefined,eval = undefined;', parsesSuccessfully, parseRaisesException(SyntaxError)), true); -assertEq(testLenientAndStrict('const arguments;', +assertEq(testLenientAndStrict('const arguments = undefined;', parsesSuccessfully, parseRaisesException(SyntaxError)), true); -assertEq(testLenientAndStrict('const x,arguments;', +assertEq(testLenientAndStrict('const x = undefined,arguments = undefined;', parsesSuccessfully, parseRaisesException(SyntaxError)), true); diff --git a/js/src/tests/js1_8_5/extensions/redeclaration-of-catch-warning.js b/js/src/tests/js1_8_5/extensions/redeclaration-of-catch-warning.js index b9951693a1c..30d1a201e02 100644 --- a/js/src/tests/js1_8_5/extensions/redeclaration-of-catch-warning.js +++ b/js/src/tests/js1_8_5/extensions/redeclaration-of-catch-warning.js @@ -30,7 +30,7 @@ function assertRedeclarationErrorThrown(expression) } } -assertRedeclarationErrorThrown("try {} catch(e) { const e; }"); +assertRedeclarationErrorThrown("try {} catch(e) { const e = undefined; }"); assertRedeclarationErrorThrown("try {} catch(e) { let e; }"); if (typeof reportCompare === "function") diff --git a/js/src/tests/js1_8_5/extensions/reflect-parse.js b/js/src/tests/js1_8_5/extensions/reflect-parse.js index a380e1aa5bd..7e7d73c2259 100644 --- a/js/src/tests/js1_8_5/extensions/reflect-parse.js +++ b/js/src/tests/js1_8_5/extensions/reflect-parse.js @@ -686,6 +686,10 @@ testParamPatternCombinations(function(n) ("[a" + n + ", ..." + "b" + n + "]"), function testVarPatternCombinations(makePattSrc, makePattPatt) { var pattSrcs = makePatternCombinations(function(n) ("x" + n), makePattSrc); var pattPatts = makePatternCombinations(function(n) ({ id: ident("x" + n), init: null }), makePattPatt); + // It's illegal to have uninitialized const declarations, so we need a + // separate set of patterns and sources. + var constSrcs = makePatternCombinations(function(n) ("x" + n + " = undefined"), makePattSrc); + var constPatts = makePatternCombinations(function(n) ({ id: ident("x" + n), init: ident("undefined") }), makePattPatt); for (var i = 0; i < pattSrcs.length; i++) { // variable declarations in blocks @@ -695,15 +699,15 @@ function testVarPatternCombinations(makePattSrc, makePattPatt) { assertLocalDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i])); assertBlockDecl("let " + pattSrcs[i].join(",") + ";", letDecl(pattPatts[i])); - assertDecl("const " + pattSrcs[i].join(",") + ";", constDecl(pattPatts[i])); + assertDecl("const " + constSrcs[i].join(",") + ";", constDecl(constPatts[i])); // variable declarations in for-loop heads assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);", forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);", letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); - assertStmt("for (const " + pattSrcs[i].join(",") + "; foo; bar);", - forStmt(constDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); + assertStmt("for (const " + constSrcs[i].join(",") + "; foo; bar);", + letStmt(constPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); } } @@ -1075,7 +1079,7 @@ assertGlobalStmt("for (;;) continue", forStmt(null, null, null, 15), { continueS assertBlockDecl("var x", "var", { variableDeclaration: function(kind) kind }); assertBlockDecl("let x", "let", { variableDeclaration: function(kind) kind }); -assertBlockDecl("const x", "const", { variableDeclaration: function(kind) kind }); +assertBlockDecl("const x = undefined", "const", { variableDeclaration: function(kind) kind }); assertBlockDecl("function f() { }", "function", { functionDeclaration: function() "function" }); assertGlobalExpr("(x,y,z)", 1, { sequenceExpression: function() 1 }); diff --git a/js/src/vm/ScopeObject.cpp b/js/src/vm/ScopeObject.cpp index f079cd9b8ce..f4fdfc2b21e 100644 --- a/js/src/vm/ScopeObject.cpp +++ b/js/src/vm/ScopeObject.cpp @@ -682,7 +682,7 @@ StaticBlockObject::create(ExclusiveContext *cx) /* static */ Shape * StaticBlockObject::addVar(ExclusiveContext *cx, Handle block, HandleId id, - unsigned index, bool *redeclared) + bool constant, unsigned index, bool *redeclared) { MOZ_ASSERT(JSID_IS_ATOM(id)); MOZ_ASSERT(index < LOCAL_INDEX_LIMIT); @@ -701,11 +701,13 @@ StaticBlockObject::addVar(ExclusiveContext *cx, Handle block * block's shape later. */ uint32_t slot = JSSLOT_FREE(&BlockObject::class_) + index; + uint32_t readonly = constant ? JSPROP_READONLY : 0; + uint32_t propFlags = readonly | JSPROP_ENUMERATE | JSPROP_PERMANENT; return NativeObject::addPropertyInternal(cx, block, id, /* getter = */ nullptr, /* setter = */ nullptr, slot, - JSPROP_ENUMERATE | JSPROP_PERMANENT, + propFlags, /* attrs = */ 0, spp, /* allowDictionary = */ false); @@ -773,18 +775,20 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, ? AtomToId(atom) : INT_TO_JSID(i)); + uint32_t propFlags; + if (!xdr->codeUint32(&propFlags)) + return false; + + bool readonly = !!(propFlags & 1); + bool redeclared; - if (!StaticBlockObject::addVar(cx, obj, id, i, &redeclared)) { + if (!StaticBlockObject::addVar(cx, obj, id, readonly, i, &redeclared)) { MOZ_ASSERT(!redeclared); return false; } - uint32_t aliased; - if (!xdr->codeUint32(&aliased)) - return false; - - MOZ_ASSERT(aliased == 0 || aliased == 1); - obj->setAliased(i, !!aliased); + bool aliased = !!(propFlags >> 1); + obj->setAliased(i, aliased); } } else { AutoShapeVector shapes(cx); @@ -811,8 +815,10 @@ js::XDRStaticBlockObject(XDRState *xdr, HandleObject enclosingScope, if (!XDRAtom(xdr, &atom)) return false; - uint32_t aliased = obj->isAliased(i); - if (!xdr->codeUint32(&aliased)) + bool aliased = obj->isAliased(i); + bool readonly = !shape->writable(); + uint32_t propFlags = (aliased << 1) | readonly; + if (!xdr->codeUint32(&propFlags)) return false; } } @@ -850,7 +856,7 @@ CloneStaticBlockObject(JSContext *cx, HandleObject enclosingScope, HandleshapeToIndex(**p); bool redeclared; - if (!StaticBlockObject::addVar(cx, clone, id, i, &redeclared)) { + if (!StaticBlockObject::addVar(cx, clone, id, !(*p)->writable(), i, &redeclared)) { MOZ_ASSERT(!redeclared); return nullptr; } diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 493ccad644a..ffe2ab67ddf 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -570,7 +570,7 @@ class StaticBlockObject : public BlockObject static const unsigned LOCAL_INDEX_LIMIT = JS_BIT(16); static Shape *addVar(ExclusiveContext *cx, Handle block, HandleId id, - unsigned index, bool *redeclared); + bool constant, unsigned index, bool *redeclared); }; class ClonedBlockObject : public BlockObject diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 2de2bb99592..09b04f99611 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -34,7 +34,7 @@ namespace js { * Nightly) and without (all others). FIXME: Bug 1066322 - Enable ES6 symbols * in all builds. */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 190; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 192; static_assert(XDR_BYTECODE_VERSION_SUBTRAHEND % 2 == 0, "see the comment above"); static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - (XDR_BYTECODE_VERSION_SUBTRAHEND diff --git a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm index 1faa07b9f56..a6110bb5fd6 100644 --- a/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm +++ b/toolkit/components/osfile/modules/osfile_shared_allthreads.jsm @@ -19,16 +19,18 @@ // Boilerplate used to be able to import this module both from the main // thread and from worker threads. + +// Since const is lexically scoped, hoist the +// conditionally-useful definition ourselves. +const Cu = typeof Components != "undefined" ? Components.utils : undefined; +const Ci = typeof Components != "undefined" ? Components.interfaces : undefined; +const Cc = typeof Components != "undefined" ? Components.classes : undefined; if (typeof Components != "undefined") { // Global definition of |exports|, to keep everybody happy. // In non-main thread, |exports| is provided by the module // loader. this.exports = {}; - const Cu = Components.utils; - const Ci = Components.interfaces; - const Cc = Components.classes; - Cu.import("resource://gre/modules/Services.jsm", this); } diff --git a/toolkit/components/places/tests/head_common.js b/toolkit/components/places/tests/head_common.js index a9da6abbf14..421c1508307 100644 --- a/toolkit/components/places/tests/head_common.js +++ b/toolkit/components/places/tests/head_common.js @@ -410,9 +410,8 @@ function shutdownPlaces(aKeepAliveConnection) } const FILENAME_BOOKMARKS_HTML = "bookmarks.html"; -let (backup_date = new Date().toLocaleFormat("%Y-%m-%d")) { - const FILENAME_BOOKMARKS_JSON = "bookmarks-" + backup_date + ".json"; -} +const FILENAME_BOOKMARKS_JSON = "bookmarks-" + + (new Date().toLocaleFormat("%Y-%m-%d")) + ".json"; /** * Creates a bookmarks.html file in the profile folder from a given source file. From adfa6449dc0037667f0b3783ccca6748127586df Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 4 Nov 2014 15:19:46 -0700 Subject: [PATCH 021/153] Bug 1091015 - Inline allocation of Typed Objects in IonMonkey, r=nmatsakis,jandem. --- js/src/builtin/TypedObject.cpp | 47 +++++--- js/src/builtin/TypedObject.h | 16 ++- js/src/jit/BaselineDebugModeOSR.cpp | 1 + js/src/jit/BaselineIC.cpp | 162 +++++++++++++++++++++++++- js/src/jit/BaselineIC.h | 79 +++++++++++++ js/src/jit/BaselineInspector.cpp | 15 +++ js/src/jit/BaselineInspector.h | 1 + js/src/jit/CodeGenerator.cpp | 24 ++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.cpp | 76 +++++++----- js/src/jit/IonBuilder.h | 8 +- js/src/jit/IonMacroAssembler.cpp | 107 ++++++++++------- js/src/jit/IonMacroAssembler.h | 4 +- js/src/jit/LIR-Common.h | 18 +++ js/src/jit/LOpcodes.h | 1 + js/src/jit/Lowering.cpp | 7 ++ js/src/jit/Lowering.h | 1 + js/src/jit/MCallOptimize.cpp | 40 +++++++ js/src/jit/MIR.h | 39 +++++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/ParallelSafetyAnalysis.cpp | 1 + js/src/jit/VMFunctions.h | 9 ++ 22 files changed, 558 insertions(+), 100 deletions(-) diff --git a/js/src/builtin/TypedObject.cpp b/js/src/builtin/TypedObject.cpp index d9c8ea66f54..a3019df00d4 100644 --- a/js/src/builtin/TypedObject.cpp +++ b/js/src/builtin/TypedObject.cpp @@ -535,7 +535,7 @@ const Class SizedArrayTypeDescr::class_ = { nullptr, nullptr, nullptr, - OutlineTypedObject::constructSized, + TypedObject::constructSized, nullptr }; @@ -641,7 +641,7 @@ ArrayMetaTypeDescr::create(JSContext *cx, int32_t size) { Rooted obj(cx); - obj = NewObjectWithProto(cx, arrayTypePrototype, nullptr, TenuredObject); + obj = NewObjectWithProto(cx, arrayTypePrototype, nullptr, SingletonObject); if (!obj) return nullptr; @@ -832,7 +832,7 @@ const Class StructTypeDescr::class_ = { nullptr, /* finalize */ nullptr, /* call */ nullptr, /* hasInstance */ - OutlineTypedObject::constructSized, + TypedObject::constructSized, nullptr /* trace */ }; @@ -1011,7 +1011,7 @@ StructMetaTypeDescr::create(JSContext *cx, Rooted descr(cx); descr = NewObjectWithProto(cx, structTypePrototype, nullptr, - TenuredObject); + SingletonObject); if (!descr) return nullptr; @@ -1251,7 +1251,7 @@ DefineSimpleTypeDescr(JSContext *cx, return false; Rooted descr(cx); - descr = NewObjectWithProto(cx, funcProto, global, TenuredObject); + descr = NewObjectWithProto(cx, funcProto, global, SingletonObject); if (!descr) return false; @@ -1562,12 +1562,13 @@ TypedObject::GetByteOffset(JSContext *cx, unsigned argc, Value *vp) /*static*/ OutlineTypedObject * OutlineTypedObject::createUnattached(JSContext *cx, HandleTypeDescr descr, - int32_t length) + int32_t length, + gc::InitialHeap heap) { if (descr->opaque()) - return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length); + return createUnattachedWithClass(cx, &OutlineOpaqueTypedObject::class_, descr, length, heap); else - return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length); + return createUnattachedWithClass(cx, &OutlineTransparentTypedObject::class_, descr, length, heap); } static JSObject * @@ -1606,7 +1607,8 @@ OutlineTypedObject::setOwnerAndData(JSObject *owner, uint8_t *data) OutlineTypedObject::createUnattachedWithClass(JSContext *cx, const Class *clasp, HandleTypeDescr type, - int32_t length) + int32_t length, + gc::InitialHeap heap) { MOZ_ASSERT(clasp == &OutlineTransparentTypedObject::class_ || clasp == &OutlineOpaqueTypedObject::class_); @@ -1616,7 +1618,8 @@ OutlineTypedObject::createUnattachedWithClass(JSContext *cx, return nullptr; gc::AllocKind allocKind = allocKindForTypeDescriptor(type); - JSObject *obj = NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind); + NewObjectKind newKind = (heap == gc::TenuredHeap) ? MaybeSingletonObject : GenericObject; + JSObject *obj = NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind, newKind); if (!obj) return nullptr; @@ -1707,19 +1710,19 @@ OutlineTypedObject::createDerived(JSContext *cx, HandleSizedTypeDescr type, } /*static*/ TypedObject * -TypedObject::createZeroed(JSContext *cx, HandleTypeDescr descr, int32_t length) +TypedObject::createZeroed(JSContext *cx, HandleTypeDescr descr, int32_t length, gc::InitialHeap heap) { // If possible, create an object with inline data. if (descr->is() && (size_t) descr->as().size() <= InlineTypedObject::MaximumSize) { - InlineTypedObject *obj = InlineTypedObject::create(cx, descr); + InlineTypedObject *obj = InlineTypedObject::create(cx, descr, heap); descr->as().initInstances(cx->runtime(), obj->inlineTypedMem(), 1); return obj; } // Create unattached wrapper object. - Rooted obj(cx, OutlineTypedObject::createUnattached(cx, descr, length)); + Rooted obj(cx, OutlineTypedObject::createUnattached(cx, descr, length, heap)); if (!obj) return nullptr; @@ -2373,7 +2376,7 @@ OutlineTypedObject::neuter(void *newData) */ /* static */ InlineTypedObject * -InlineTypedObject::create(JSContext *cx, HandleTypeDescr descr) +InlineTypedObject::create(JSContext *cx, HandleTypeDescr descr, gc::InitialHeap heap) { gc::AllocKind allocKind = allocKindForTypeDescriptor(descr); @@ -2385,13 +2388,27 @@ InlineTypedObject::create(JSContext *cx, HandleTypeDescr descr) ? &InlineOpaqueTypedObject::class_ : &InlineTransparentTypedObject::class_; - RootedObject obj(cx, NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind)); + NewObjectKind newKind = (heap == gc::TenuredHeap) ? MaybeSingletonObject : GenericObject; + RootedObject obj(cx, NewObjectWithClassProto(cx, clasp, proto, nullptr, allocKind, newKind)); if (!obj) return nullptr; return &obj->as(); } +/* static */ InlineTypedObject * +InlineTypedObject::createCopy(JSContext *cx, Handle templateObject, + gc::InitialHeap heap) +{ + Rooted descr(cx, &templateObject->typeDescr()); + InlineTypedObject *res = create(cx, descr, heap); + if (!res) + return nullptr; + + memcpy(res->inlineTypedMem(), templateObject->inlineTypedMem(), templateObject->size()); + return res; +} + /* static */ void InlineTypedObject::obj_trace(JSTracer *trc, JSObject *object) { diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 02af9c11769..80f09624a43 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -676,7 +676,8 @@ class TypedObject : public JSObject // Creates a new typed object whose memory is freshly allocated and // initialized with zeroes (or, in the case of references, an appropriate // default value). - static TypedObject *createZeroed(JSContext *cx, HandleTypeDescr typeObj, int32_t length); + static TypedObject *createZeroed(JSContext *cx, HandleTypeDescr typeObj, int32_t length, + gc::InitialHeap heap = gc::DefaultHeap); // User-accessible constructor (`new TypeDescriptor(...)`) used for sized // types. Note that the callee here is the type descriptor. @@ -753,7 +754,8 @@ class OutlineTypedObject : public TypedObject static OutlineTypedObject *createUnattachedWithClass(JSContext *cx, const Class *clasp, HandleTypeDescr type, - int32_t length); + int32_t length, + gc::InitialHeap heap = gc::DefaultHeap); // Creates an unattached typed object or handle (depending on the // type parameter T). Note that it is only legal for unattached @@ -764,7 +766,7 @@ class OutlineTypedObject : public TypedObject // - type: type object for resulting object // - length: 0 unless this is an array, otherwise the length static OutlineTypedObject *createUnattached(JSContext *cx, HandleTypeDescr type, - int32_t length); + int32_t length, gc::InitialHeap heap = gc::DefaultHeap); // Creates a typedObj that aliases the memory pointed at by `owner` // at the given offset. The typedObj will be a handle iff type is a @@ -810,7 +812,8 @@ class InlineTypedObject : public TypedObject uint8_t data_[1]; public: - static const size_t MaximumSize = NativeObject::MAX_FIXED_SLOTS * sizeof(Value); + static const size_t MaximumSize = + sizeof(NativeObject) - sizeof(TypedObject) + NativeObject::MAX_FIXED_SLOTS * sizeof(Value); static gc::AllocKind allocKindForTypeDescriptor(TypeDescr *descr) { size_t nbytes = descr->as().size(); @@ -833,7 +836,10 @@ class InlineTypedObject : public TypedObject return offsetof(InlineTypedObject, data_); } - static InlineTypedObject *create(JSContext *cx, HandleTypeDescr descr); + static InlineTypedObject *create(JSContext *cx, HandleTypeDescr descr, + gc::InitialHeap heap = gc::DefaultHeap); + static InlineTypedObject *createCopy(JSContext *cx, Handle templateObject, + gc::InitialHeap heap); }; // Class for a transparent typed object with inline data, which may have a diff --git a/js/src/jit/BaselineDebugModeOSR.cpp b/js/src/jit/BaselineDebugModeOSR.cpp index a558e6b6a5b..b6f784b6ff3 100644 --- a/js/src/jit/BaselineDebugModeOSR.cpp +++ b/js/src/jit/BaselineDebugModeOSR.cpp @@ -533,6 +533,7 @@ RecompileBaselineScriptForDebugMode(JSContext *cx, JSScript *script) _(Call_Scripted) \ _(Call_AnyScripted) \ _(Call_Native) \ + _(Call_ClassHook) \ _(Call_ScriptedApplyArray) \ _(Call_ScriptedApplyArguments) \ _(Call_ScriptedFunCall) \ diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 6b37e6998ad..7325f26deb2 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -201,6 +201,12 @@ ICStub::trace(JSTracer *trc) MarkObject(trc, &callStub->templateObject(), "baseline-callnative-template"); break; } + case ICStub::Call_ClassHook: { + ICCall_ClassHook *callStub = toCall_ClassHook(); + if (callStub->templateObject()) + MarkObject(trc, &callStub->templateObject(), "baseline-callclasshook-template"); + break; + } case ICStub::Call_StringSplit: { ICCall_StringSplit *callStub = toCall_StringSplit(); MarkObject(trc, &callStub->templateObject(), "baseline-callstringsplit-template"); @@ -704,6 +710,7 @@ ICStubCompiler::guardProfilingEnabled(MacroAssembler &masm, Register scratch, La MOZ_ASSERT(kind == ICStub::Call_Scripted || kind == ICStub::Call_AnyScripted || kind == ICStub::Call_Native || + kind == ICStub::Call_ClassHook || kind == ICStub::Call_ScriptedApplyArray || kind == ICStub::Call_ScriptedApplyArguments || kind == ICStub::Call_ScriptedFunCall || @@ -8582,6 +8589,22 @@ GetTemplateObjectForNative(JSContext *cx, HandleScript script, jsbytecode *pc, return true; } +static bool +GetTemplateObjectForClassHook(JSContext *cx, JSNative hook, CallArgs &args, + MutableHandleObject templateObject) +{ + if (hook == TypedObject::constructSized) { + Rooted descr(cx, &args.callee().as()); + JSObject *obj = TypedObject::createZeroed(cx, descr, 1, gc::TenuredHeap); + if (!obj) + return false; + templateObject.set(obj); + return true; + } + + return true; +} + static bool IsOptimizableCallStringSplit(Value callee, Value thisv, int argc, Value *args) { @@ -8631,8 +8654,27 @@ TryAttachCallStub(JSContext *cx, ICCall_Fallback *stub, HandleScript script, jsb return true; RootedObject obj(cx, &callee.toObject()); - if (!obj->is()) + if (!obj->is()) { + if (JSNative hook = constructing ? obj->constructHook() : obj->callHook()) { + if (op != JSOP_FUNAPPLY && !isSpread && !useNewType) { + RootedObject templateObject(cx); + CallArgs args = CallArgsFromVp(argc, vp); + if (!GetTemplateObjectForClassHook(cx, hook, args, &templateObject)) + return false; + + JitSpew(JitSpew_BaselineIC, " Generating Call_ClassHook stub"); + ICCall_ClassHook::Compiler compiler(cx, stub->fallbackMonitorStub()->firstMonitorStub(), + obj->getClass(), hook, templateObject, constructing); + ICStub *newStub = compiler.getStub(compiler.getStubSpace(script)); + if (!newStub) + return false; + + stub->addNewStub(newStub); + return true; + } + } return true; + } RootedFunction fun(cx, &obj->as()); @@ -9810,6 +9852,97 @@ ICCall_Native::Compiler::generateStubCode(MacroAssembler &masm) return true; } +bool +ICCall_ClassHook::Compiler::generateStubCode(MacroAssembler &masm) +{ + Label failure; + GeneralRegisterSet regs(availableGeneralRegs(0)); + + Register argcReg = R0.scratchReg(); + regs.take(argcReg); + regs.takeUnchecked(BaselineTailCallReg); + + // Load the callee in R1. + BaseIndex calleeSlot(BaselineStackReg, argcReg, TimesEight, ICStackValueOffset + sizeof(Value)); + masm.loadValue(calleeSlot, R1); + regs.take(R1); + + masm.branchTestObject(Assembler::NotEqual, R1, &failure); + + // Ensure the callee's class matches the one in this stub. + Register callee = masm.extractObject(R1, ExtractTemp0); + Register scratch = regs.takeAny(); + masm.loadObjClass(callee, scratch); + masm.branchPtr(Assembler::NotEqual, + Address(BaselineStubReg, ICCall_ClassHook::offsetOfClass()), + scratch, &failure); + + regs.add(R1); + regs.takeUnchecked(callee); + + // Push a stub frame so that we can perform a non-tail call. + // Note that this leaves the return address in TailCallReg. + enterStubFrame(masm, regs.getAny()); + + regs.add(scratch); + pushCallArguments(masm, regs, argcReg); + regs.take(scratch); + + if (isConstructing_) { + // Stack looks like: [ ..., Arg0Val, ThisVal, CalleeVal ] + // Replace ThisVal with MagicValue(JS_IS_CONSTRUCTING) + masm.storeValue(MagicValue(JS_IS_CONSTRUCTING), Address(BaselineStackReg, sizeof(Value))); + } + + masm.checkStackAlignment(); + + // Native functions have the signature: + // + // bool (*)(JSContext *, unsigned, Value *vp) + // + // Where vp[0] is space for callee/return value, vp[1] is |this|, and vp[2] onward + // are the function arguments. + + // Initialize vp. + Register vpReg = regs.takeAny(); + masm.movePtr(StackPointer, vpReg); + + // Construct a native exit frame. + masm.push(argcReg); + + EmitCreateStubFrameDescriptor(masm, scratch); + masm.push(scratch); + masm.push(BaselineTailCallReg); + masm.enterFakeExitFrame(IonNativeExitFrameLayout::Token()); + + // If needed, update SPS Profiler frame entry. At this point, BaselineTailCallReg + // and scratch can be clobbered. + emitProfilingUpdate(masm, BaselineTailCallReg, scratch, ICCall_Native::offsetOfPCOffset()); + + // Execute call. + masm.setupUnalignedABICall(3, scratch); + masm.loadJSContext(scratch); + masm.passABIArg(scratch); + masm.passABIArg(argcReg); + masm.passABIArg(vpReg); + masm.callWithABI(Address(BaselineStubReg, ICCall_ClassHook::offsetOfNative())); + + // Test for failure. + masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); + + // Load the return value into R0. + masm.loadValue(Address(StackPointer, IonNativeExitFrameLayout::offsetOfResult()), R0); + + leaveStubFrame(masm); + + // Enter type monitor IC to type-check result. + EmitEnterTypeMonitorIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + bool ICCall_ScriptedApplyArray::Compiler::generateStubCode(MacroAssembler &masm) { @@ -11157,6 +11290,33 @@ ICCall_Native::Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub other.pcOffset_); } +ICCall_ClassHook::ICCall_ClassHook(JitCode *stubCode, ICStub *firstMonitorStub, + const Class *clasp, Native native, HandleObject templateObject) + : ICMonitoredStub(ICStub::Call_ClassHook, stubCode, firstMonitorStub), + clasp_(clasp), + native_(JS_FUNC_TO_DATA_PTR(void *, native)), + templateObject_(templateObject) +{ +#if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR) + // The simulator requires VM calls to be redirected to a special swi + // instruction to handle them. To make this work, we store the redirected + // pointer in the stub. + native_ = Simulator::RedirectNativeFunction(native_, Args_General3); +#endif +} + +/* static */ ICCall_ClassHook * +ICCall_ClassHook::Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub, + ICCall_ClassHook &other) +{ + RootedObject templateObject(cx, other.templateObject_); + ICCall_ClassHook *res = New(space, other.jitCode(), firstMonitorStub, + other.clasp(), nullptr, templateObject); + if (res) + res->native_ = other.native(); + return res; +} + /* static */ ICCall_ScriptedApplyArray * ICCall_ScriptedApplyArray::Clone(JSContext *, ICStubSpace *space, ICStub *firstMonitorStub, ICCall_ScriptedApplyArray &other) diff --git a/js/src/jit/BaselineIC.h b/js/src/jit/BaselineIC.h index d027875395f..e30b067a7bb 100644 --- a/js/src/jit/BaselineIC.h +++ b/js/src/jit/BaselineIC.h @@ -375,6 +375,7 @@ class ICEntry _(Call_Scripted) \ _(Call_AnyScripted) \ _(Call_Native) \ + _(Call_ClassHook) \ _(Call_ScriptedApplyArray) \ _(Call_ScriptedApplyArguments) \ _(Call_ScriptedFunCall) \ @@ -790,6 +791,7 @@ class ICStub case Call_Scripted: case Call_AnyScripted: case Call_Native: + case Call_ClassHook: case Call_ScriptedApplyArray: case Call_ScriptedApplyArguments: case Call_ScriptedFunCall: @@ -5897,6 +5899,83 @@ class ICCall_Native : public ICMonitoredStub }; }; +class ICCall_ClassHook : public ICMonitoredStub +{ + friend class ICStubSpace; + + protected: + const Class *clasp_; + void *native_; + HeapPtrObject templateObject_; + + ICCall_ClassHook(JitCode *stubCode, ICStub *firstMonitorStub, + const Class *clasp, Native native, HandleObject templateObject); + + public: + static inline ICCall_ClassHook *New(ICStubSpace *space, + JitCode *code, ICStub *firstMonitorStub, + const Class *clasp, Native native, + HandleObject templateObject) + { + if (!code) + return nullptr; + return space->allocate(code, firstMonitorStub, + clasp, native, templateObject); + } + + static ICCall_ClassHook *Clone(JSContext *cx, ICStubSpace *space, ICStub *firstMonitorStub, + ICCall_ClassHook &other); + + const Class *clasp() { + return clasp_; + } + void *native() { + return native_; + } + HeapPtrObject &templateObject() { + return templateObject_; + } + + static size_t offsetOfClass() { + return offsetof(ICCall_ClassHook, clasp_); + } + static size_t offsetOfNative() { + return offsetof(ICCall_ClassHook, native_); + } + + // Compiler for this stub kind. + class Compiler : public ICCallStubCompiler { + protected: + ICStub *firstMonitorStub_; + bool isConstructing_; + const Class *clasp_; + Native native_; + RootedObject templateObject_; + bool generateStubCode(MacroAssembler &masm); + + virtual int32_t getKey() const { + return static_cast(kind) | (static_cast(isConstructing_) << 16); + } + + public: + Compiler(JSContext *cx, ICStub *firstMonitorStub, + const Class *clasp, Native native, HandleObject templateObject, + bool isConstructing) + : ICCallStubCompiler(cx, ICStub::Call_ClassHook), + firstMonitorStub_(firstMonitorStub), + isConstructing_(isConstructing), + clasp_(clasp), + native_(native), + templateObject_(cx, templateObject) + { } + + ICStub *getStub(ICStubSpace *space) { + return ICCall_ClassHook::New(space, getStubCode(), firstMonitorStub_, + clasp_, native_, templateObject_); + } + }; +}; + class ICCall_ScriptedApplyArray : public ICMonitoredStub { friend class ICStubSpace; diff --git a/js/src/jit/BaselineInspector.cpp b/js/src/jit/BaselineInspector.cpp index 0a896afca0b..240088d1b0c 100644 --- a/js/src/jit/BaselineInspector.cpp +++ b/js/src/jit/BaselineInspector.cpp @@ -455,6 +455,21 @@ BaselineInspector::getTemplateObjectForNative(jsbytecode *pc, Native native) return nullptr; } +JSObject * +BaselineInspector::getTemplateObjectForClassHook(jsbytecode *pc, const Class *clasp) +{ + if (!hasBaselineScript()) + return nullptr; + + const ICEntry &entry = icEntryFromPC(pc); + for (ICStub *stub = entry.firstStub(); stub; stub = stub->next()) { + if (stub->isCall_ClassHook() && stub->toCall_ClassHook()->clasp() == clasp) + return stub->toCall_ClassHook()->templateObject(); + } + + return nullptr; +} + DeclEnvObject * BaselineInspector::templateDeclEnvObject() { diff --git a/js/src/jit/BaselineInspector.h b/js/src/jit/BaselineInspector.h index 40f83042782..abcf1acf256 100644 --- a/js/src/jit/BaselineInspector.h +++ b/js/src/jit/BaselineInspector.h @@ -111,6 +111,7 @@ class BaselineInspector NativeObject *getTemplateObject(jsbytecode *pc); NativeObject *getTemplateObjectForNative(jsbytecode *pc, Native native); + JSObject *getTemplateObjectForClassHook(jsbytecode *pc, const Class *clasp); DeclEnvObject *templateDeclEnvObject(); CallObject *templateCallObject(); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 3a2bffc66e1..88c72fb731f 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4452,6 +4452,30 @@ CodeGenerator::visitOutOfLineNewObject(OutOfLineNewObject *ool) return true; } +typedef InlineTypedObject *(*NewTypedObjectFn)(JSContext *, Handle, gc::InitialHeap); +static const VMFunction NewTypedObjectInfo = + FunctionInfo(InlineTypedObject::createCopy); + +bool +CodeGenerator::visitNewTypedObject(LNewTypedObject *lir) +{ + Register object = ToRegister(lir->output()); + Register temp = ToRegister(lir->temp()); + InlineTypedObject *templateObject = lir->mir()->templateObject(); + gc::InitialHeap initialHeap = lir->mir()->initialHeap(); + + OutOfLineCode *ool = oolCallVM(NewTypedObjectInfo, lir, + (ArgList(), ImmGCPtr(templateObject), Imm32(initialHeap)), + StoreRegisterTo(object)); + if (!ool) + return false; + + masm.createGCObject(object, temp, templateObject, initialHeap, ool->entry()); + + masm.bind(ool->rejoin()); + return true; +} + typedef js::DeclEnvObject *(*NewDeclEnvObjectFn)(JSContext *, HandleFunction, gc::InitialHeap); static const VMFunction NewDeclEnvObjectInfo = FunctionInfo(DeclEnvObject::createTemplateObject); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 40a0b38ac21..4e24d799c61 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -155,6 +155,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitNewObjectVMCall(LNewObject *lir); bool visitNewObject(LNewObject *lir); bool visitOutOfLineNewObject(OutOfLineNewObject *ool); + bool visitNewTypedObject(LNewTypedObject *lir); bool visitNewDeclEnvObject(LNewDeclEnvObject *lir); bool visitNewCallObject(LNewCallObject *lir); bool visitNewSingletonCallObject(LNewSingletonCallObject *lir); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index ac3dbc73230..b31a236c3f0 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -292,14 +292,7 @@ IonBuilder::getPolyCallTargets(types::TemporaryTypeSet *calleeTypes, bool constr return false; for(unsigned i = 0; i < objCount; i++) { JSObject *obj = calleeTypes->getSingleObject(i); - JSFunction *fun; - if (obj) { - if (!obj->is()) { - targets.clear(); - return true; - } - fun = &obj->as(); - } else { + if (!obj) { types::TypeObject *typeObj = calleeTypes->getTypeObject(i); MOZ_ASSERT(typeObj); if (!typeObj->interpretedFunction) { @@ -307,19 +300,19 @@ IonBuilder::getPolyCallTargets(types::TemporaryTypeSet *calleeTypes, bool constr return true; } - fun = typeObj->interpretedFunction; + obj = typeObj->interpretedFunction; *gotLambda = true; } - // Don't optimize if we're constructing and the callee is not a - // constructor, so that CallKnown does not have to handle this case - // (it should always throw). - if (constructing && !fun->isInterpretedConstructor() && !fun->isNativeConstructor()) { + // Don't optimize if the callee is not callable or constructable per + // the manner it is being invoked, so that CallKnown does not have to + // handle these cases (they will always throw). + if (constructing ? !obj->isConstructor() : !obj->isCallable()) { targets.clear(); return true; } - DebugOnly appendOk = targets.append(fun); + DebugOnly appendOk = targets.append(obj); MOZ_ASSERT(appendOk); } @@ -4417,12 +4410,18 @@ IonBuilder::patchInlinedReturns(CallInfo &callInfo, MIRGraphReturns &returns, MB } IonBuilder::InliningDecision -IonBuilder::makeInliningDecision(JSFunction *target, CallInfo &callInfo) +IonBuilder::makeInliningDecision(JSObject *targetArg, CallInfo &callInfo) { // When there is no target, inlining is impossible. - if (target == nullptr) + if (targetArg == nullptr) return InliningDecision_DontInline; + // Inlining non-function targets is handled by inlineNonFunctionCall(). + if (!targetArg->is()) + return InliningDecision_Inline; + + JSFunction *target = &targetArg->as(); + // Never inline during the arguments usage analysis. if (info().executionMode() == ArgumentsUsageAnalysis) return InliningDecision_DontInline; @@ -4525,7 +4524,7 @@ IonBuilder::selectInliningTargets(ObjectVector &targets, CallInfo &callInfo, Boo return true; for (size_t i = 0; i < targets.length(); i++) { - JSFunction *target = &targets[i]->as(); + JSObject *target = targets[i]; bool inlineable; InliningDecision decision = makeInliningDecision(target, callInfo); switch (decision) { @@ -4542,11 +4541,16 @@ IonBuilder::selectInliningTargets(ObjectVector &targets, CallInfo &callInfo, Boo MOZ_CRASH("Unhandled InliningDecision value!"); } - // Enforce a maximum inlined bytecode limit at the callsite. - if (inlineable && target->isInterpreted()) { - totalSize += target->nonLazyScript()->length(); - if (totalSize > optimizationInfo().inlineMaxTotalBytecodeLength()) - inlineable = false; + if (target->is()) { + // Enforce a maximum inlined bytecode limit at the callsite. + if (inlineable && target->as().isInterpreted()) { + totalSize += target->as().nonLazyScript()->length(); + if (totalSize > optimizationInfo().inlineMaxTotalBytecodeLength()) + inlineable = false; + } + } else { + // Non-function targets are not supported by polymorphic inlining. + inlineable = false; } choiceSet.append(inlineable); @@ -4674,9 +4678,12 @@ IonBuilder::getInlineableGetPropertyCache(CallInfo &callInfo) } IonBuilder::InliningStatus -IonBuilder::inlineSingleCall(CallInfo &callInfo, JSFunction *target) +IonBuilder::inlineSingleCall(CallInfo &callInfo, JSObject *targetArg) { - // Expects formals to be popped and wrapped. + if (!targetArg->is()) + return inlineNonFunctionCall(callInfo, targetArg); + + JSFunction *target = &targetArg->as(); if (target->isNative()) return inlineNativeCall(callInfo, target); @@ -4701,7 +4708,7 @@ IonBuilder::inlineCallsite(ObjectVector &targets, ObjectVector &originals, // Inline single targets -- unless they derive from a cache, in which case // avoiding the cache and guarding is still faster. if (!propCache.get() && targets.length() == 1) { - JSFunction *target = &targets[0]->as(); + JSObject *target = targets[0]; InliningDecision decision = makeInliningDecision(target, callInfo); switch (decision) { case InliningDecision_Error: @@ -5506,14 +5513,19 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing) bool hasClones = false; ObjectVector targets(alloc()); for (uint32_t i = 0; i < originals.length(); i++) { - JSFunction *fun = &originals[i]->as(); - if (fun->hasScript() && fun->nonLazyScript()->shouldCloneAtCallsite()) { - if (JSFunction *clone = ExistingCloneFunctionAtCallsite(compartment->callsiteClones(), fun, script(), pc)) { - fun = clone; - hasClones = true; + JSObject *obj = originals[i]; + if (obj->is()) { + JSFunction *fun = &obj->as(); + if (fun->hasScript() && fun->nonLazyScript()->shouldCloneAtCallsite()) { + if (JSFunction *clone = ExistingCloneFunctionAtCallsite(compartment->callsiteClones(), + fun, script(), pc)) + { + obj = clone; + hasClones = true; + } } } - if (!targets.append(fun)) + if (!targets.append(obj)) return false; } @@ -5530,7 +5542,7 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing) // No inline, just make the call. JSFunction *target = nullptr; - if (targets.length() == 1) + if (targets.length() == 1 && targets[0]->is()) target = &targets[0]->as(); if (target && status == InliningStatus_WarmUpCountTooLow) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 3b66ebd7f6b..6a7fc3027d0 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -676,7 +676,7 @@ class IonBuilder // Oracles. InliningDecision canInlineTarget(JSFunction *target, CallInfo &callInfo); - InliningDecision makeInliningDecision(JSFunction *target, CallInfo &callInfo); + InliningDecision makeInliningDecision(JSObject *target, CallInfo &callInfo); bool selectInliningTargets(ObjectVector &targets, CallInfo &callInfo, BoolVector &choiceSet, uint32_t *numInlineable); @@ -746,11 +746,12 @@ class IonBuilder // ForkJoin intrinsics InliningStatus inlineForkJoinGetSlice(CallInfo &callInfo); - // TypedObject intrinsics. + // TypedObject intrinsics and natives. InliningStatus inlineObjectIsTypeDescr(CallInfo &callInfo); InliningStatus inlineSetTypedObjectOffset(CallInfo &callInfo); bool elementAccessIsTypedObjectArrayOfScalarType(MDefinition* obj, MDefinition* id, ScalarTypeDescr::Type *arrayType); + InliningStatus inlineConstructTypedObject(CallInfo &callInfo, SizedTypeDescr *target); // Utility intrinsics. InliningStatus inlineIsCallable(CallInfo &callInfo); @@ -777,8 +778,9 @@ class IonBuilder // Main inlining functions InliningStatus inlineNativeCall(CallInfo &callInfo, JSFunction *target); InliningStatus inlineNativeGetter(CallInfo &callInfo, JSFunction *target); + InliningStatus inlineNonFunctionCall(CallInfo &callInfo, JSObject *target); bool inlineScriptedCall(CallInfo &callInfo, JSFunction *target); - InliningStatus inlineSingleCall(CallInfo &callInfo, JSFunction *target); + InliningStatus inlineSingleCall(CallInfo &callInfo, JSObject *target); // Call functions InliningStatus inlineCallsite(ObjectVector &targets, ObjectVector &originals, diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 879c8c40f2b..397bd044f7e 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -787,18 +787,22 @@ MacroAssembler::newGCThing(Register result, Register temp, NativeObject *templat } void -MacroAssembler::createGCObject(Register obj, Register temp, NativeObject *templateObj, +MacroAssembler::createGCObject(Register obj, Register temp, JSObject *templateObj, gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots) { - uint32_t nDynamicSlots = templateObj->numDynamicSlots(); gc::AllocKind allocKind = templateObj->asTenured().getAllocKind(); MOZ_ASSERT(allocKind >= gc::FINALIZE_OBJECT0 && allocKind <= gc::FINALIZE_OBJECT_LAST); - // Arrays with copy on write elements do not need fixed space for an - // elements header. The template object, which owns the original elements, - // might have another allocation kind. - if (templateObj->denseElementsAreCopyOnWrite()) - allocKind = gc::FINALIZE_OBJECT0_BACKGROUND; + uint32_t nDynamicSlots = 0; + if (templateObj->isNative()) { + nDynamicSlots = templateObj->as().numDynamicSlots(); + + // Arrays with copy on write elements do not need fixed space for an + // elements header. The template object, which owns the original + // elements, might have another allocation kind. + if (templateObj->as().denseElementsAreCopyOnWrite()) + allocKind = gc::FINALIZE_OBJECT0_BACKGROUND; + } allocateObject(obj, temp, allocKind, nDynamicSlots, initialHeap, fail); initGCThing(obj, temp, templateObj, initFixedSlots); @@ -1030,54 +1034,73 @@ MacroAssembler::initGCSlots(Register obj, Register slots, NativeObject *template } void -MacroAssembler::initGCThing(Register obj, Register slots, NativeObject *templateObj, +MacroAssembler::initGCThing(Register obj, Register slots, JSObject *templateObj, bool initFixedSlots) { // Fast initialization of an empty object returned by allocateObject(). - MOZ_ASSERT_IF(!templateObj->denseElementsAreCopyOnWrite(), !templateObj->hasDynamicElements()); - storePtr(ImmGCPtr(templateObj->lastProperty()), Address(obj, JSObject::offsetOfShape())); storePtr(ImmGCPtr(templateObj->type()), Address(obj, JSObject::offsetOfType())); - if (templateObj->hasDynamicSlots()) - storePtr(slots, Address(obj, NativeObject::offsetOfSlots())); - else - storePtr(ImmPtr(nullptr), Address(obj, NativeObject::offsetOfSlots())); - if (templateObj->denseElementsAreCopyOnWrite()) { - storePtr(ImmPtr((const Value *) templateObj->getDenseElements()), - Address(obj, NativeObject::offsetOfElements())); - } else if (templateObj->is()) { - Register temp = slots; - MOZ_ASSERT(!templateObj->getDenseInitializedLength()); + if (templateObj->isNative()) { + NativeObject *ntemplate = &templateObj->as(); + MOZ_ASSERT_IF(!ntemplate->denseElementsAreCopyOnWrite(), !ntemplate->hasDynamicElements()); - int elementsOffset = NativeObject::offsetOfFixedElements(); + if (ntemplate->hasDynamicSlots()) + storePtr(slots, Address(obj, NativeObject::offsetOfSlots())); + else + storePtr(ImmPtr(nullptr), Address(obj, NativeObject::offsetOfSlots())); - computeEffectiveAddress(Address(obj, elementsOffset), temp); - storePtr(temp, Address(obj, NativeObject::offsetOfElements())); + if (ntemplate->denseElementsAreCopyOnWrite()) { + storePtr(ImmPtr((const Value *) ntemplate->getDenseElements()), + Address(obj, NativeObject::offsetOfElements())); + } else if (ntemplate->is()) { + Register temp = slots; + MOZ_ASSERT(!ntemplate->getDenseInitializedLength()); - // Fill in the elements header. - store32(Imm32(templateObj->getDenseCapacity()), - Address(obj, elementsOffset + ObjectElements::offsetOfCapacity())); - store32(Imm32(templateObj->getDenseInitializedLength()), - Address(obj, elementsOffset + ObjectElements::offsetOfInitializedLength())); - store32(Imm32(templateObj->as().length()), - Address(obj, elementsOffset + ObjectElements::offsetOfLength())); - store32(Imm32(templateObj->shouldConvertDoubleElements() - ? ObjectElements::CONVERT_DOUBLE_ELEMENTS - : 0), - Address(obj, elementsOffset + ObjectElements::offsetOfFlags())); - MOZ_ASSERT(!templateObj->hasPrivate()); - } else { - storePtr(ImmPtr(emptyObjectElements), Address(obj, NativeObject::offsetOfElements())); + int elementsOffset = NativeObject::offsetOfFixedElements(); - initGCSlots(obj, slots, templateObj, initFixedSlots); + computeEffectiveAddress(Address(obj, elementsOffset), temp); + storePtr(temp, Address(obj, NativeObject::offsetOfElements())); - if (templateObj->hasPrivate()) { - uint32_t nfixed = templateObj->numFixedSlots(); - storePtr(ImmPtr(templateObj->getPrivate()), - Address(obj, NativeObject::getPrivateDataOffset(nfixed))); + // Fill in the elements header. + store32(Imm32(ntemplate->getDenseCapacity()), + Address(obj, elementsOffset + ObjectElements::offsetOfCapacity())); + store32(Imm32(ntemplate->getDenseInitializedLength()), + Address(obj, elementsOffset + ObjectElements::offsetOfInitializedLength())); + store32(Imm32(ntemplate->as().length()), + Address(obj, elementsOffset + ObjectElements::offsetOfLength())); + store32(Imm32(ntemplate->shouldConvertDoubleElements() + ? ObjectElements::CONVERT_DOUBLE_ELEMENTS + : 0), + Address(obj, elementsOffset + ObjectElements::offsetOfFlags())); + MOZ_ASSERT(!ntemplate->hasPrivate()); + } else { + storePtr(ImmPtr(emptyObjectElements), Address(obj, NativeObject::offsetOfElements())); + + initGCSlots(obj, slots, ntemplate, initFixedSlots); + + if (ntemplate->hasPrivate()) { + uint32_t nfixed = ntemplate->numFixedSlots(); + storePtr(ImmPtr(ntemplate->getPrivate()), + Address(obj, NativeObject::getPrivateDataOffset(nfixed))); + } } + } else if (templateObj->is()) { + InlineTypedObject *ntemplate = &templateObj->as(); + + // Memcpy the contents of the template object to the new object. + size_t nbytes = ntemplate->size(); + size_t offset = 0; + while (nbytes) { + uintptr_t value = *(uintptr_t *)(ntemplate->inlineTypedMem() + offset); + storePtr(ImmWord(value), + Address(obj, InlineTypedObject::offsetOfDataStart() + offset)); + nbytes = (nbytes < sizeof(uintptr_t)) ? 0 : nbytes - sizeof(uintptr_t); + offset += sizeof(uintptr_t); + } + } else { + MOZ_CRASH("Unknown object"); } #ifdef JS_GC_TRACE diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index c1aa469a4f8..a303a814fd9 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -816,12 +816,12 @@ class MacroAssembler : public MacroAssemblerSpecific public: void callMallocStub(size_t nbytes, Register result, Label *fail); void callFreeStub(Register slots); - void createGCObject(Register result, Register temp, NativeObject *templateObj, + void createGCObject(Register result, Register temp, JSObject *templateObj, gc::InitialHeap initialHeap, Label *fail, bool initFixedSlots = true); void newGCThing(Register result, Register temp, NativeObject *templateObj, gc::InitialHeap initialHeap, Label *fail); - void initGCThing(Register obj, Register temp, NativeObject *templateObj, + void initGCThing(Register obj, Register temp, JSObject *templateObj, bool initFixedSlots = true); void newGCString(Register result, Register temp, Label *fail); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 34ac5a5a1ca..686b1f792d6 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -730,6 +730,24 @@ class LNewObject : public LInstructionHelper<1, 0, 1> } }; +class LNewTypedObject : public LInstructionHelper<1, 0, 1> +{ + public: + LIR_HEADER(NewTypedObject) + + explicit LNewTypedObject(const LDefinition &temp) { + setTemp(0, temp); + } + + const LDefinition *temp() { + return getTemp(0); + } + + MNewTypedObject *mir() const { + return mir_->toNewTypedObject(); + } +}; + class LNewPar : public LInstructionHelper<1, 1, 2> { public: diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index 5285616b66c..c93166cf408 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -48,6 +48,7 @@ _(NewArrayCopyOnWrite) \ _(ArraySplice) \ _(NewObject) \ + _(NewTypedObject) \ _(NewDeclEnvObject) \ _(NewCallObject) \ _(NewSingletonCallObject) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 2e1c807332b..8a21c7cc263 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -175,6 +175,13 @@ LIRGenerator::visitNewObject(MNewObject *ins) return define(lir, ins) && assignSafepoint(lir, ins); } +bool +LIRGenerator::visitNewTypedObject(MNewTypedObject *ins) +{ + LNewTypedObject *lir = new(alloc()) LNewTypedObject(temp()); + return define(lir, ins) && assignSafepoint(lir, ins); +} + bool LIRGenerator::visitNewDeclEnvObject(MNewDeclEnvObject *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index ca8523a0091..5b9620d230f 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -73,6 +73,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitNewArray(MNewArray *ins); bool visitNewArrayCopyOnWrite(MNewArrayCopyOnWrite *ins); bool visitNewObject(MNewObject *ins); + bool visitNewTypedObject(MNewTypedObject *ins); bool visitNewDeclEnvObject(MNewDeclEnvObject *ins); bool visitNewCallObject(MNewCallObject *ins); bool visitNewRunOnceCallObject(MNewRunOnceCallObject *ins); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index e3b9ff54c59..68bd67004b1 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -280,6 +280,18 @@ IonBuilder::inlineNativeGetter(CallInfo &callInfo, JSFunction *target) return InliningStatus_NotInlined; } +IonBuilder::InliningStatus +IonBuilder::inlineNonFunctionCall(CallInfo &callInfo, JSObject *target) +{ + // Inline a call to a non-function object, invoking the object's call or + // construct hook. + + if (callInfo.constructing() && target->constructHook() == TypedObject::constructSized) + return inlineConstructTypedObject(callInfo, &target->as()); + + return InliningStatus_NotInlined; +} + types::TemporaryTypeSet * IonBuilder::getInlineReturnTypeSet() { @@ -2506,5 +2518,33 @@ IonBuilder::inlineIsConstructing(CallInfo &callInfo) return InliningStatus_Inlined; } +IonBuilder::InliningStatus +IonBuilder::inlineConstructTypedObject(CallInfo &callInfo, SizedTypeDescr *descr) +{ + // Only inline default constructors for now. + if (callInfo.argc() != 0) + return InliningStatus_NotInlined; + + if (size_t(descr->size()) > InlineTypedObject::MaximumSize) + return InliningStatus_NotInlined; + + JSObject *obj = inspector->getTemplateObjectForClassHook(pc, descr->getClass()); + if (!obj || !obj->is()) + return InliningStatus_NotInlined; + + InlineTypedObject *templateObject = &obj->as(); + if (&templateObject->typeDescr() != descr) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MNewTypedObject *ins = MNewTypedObject::New(alloc(), constraints(), templateObject, + templateObject->type()->initialHeap(constraints())); + current->add(ins); + current->push(ins); + + return InliningStatus_Inlined; +} + } // namespace jit } // namespace js diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index cade1c27621..16b3ddd6c36 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -2736,6 +2736,45 @@ class MNewPar : public MUnaryInstruction } }; +class MNewTypedObject : public MNullaryInstruction +{ + AlwaysTenured templateObject_; + gc::InitialHeap initialHeap_; + + MNewTypedObject(types::CompilerConstraintList *constraints, + InlineTypedObject *templateObject, + gc::InitialHeap initialHeap) + : templateObject_(templateObject), + initialHeap_(initialHeap) + { + setResultType(MIRType_Object); + setResultTypeSet(MakeSingletonTypeSet(constraints, templateObject)); + } + + public: + INSTRUCTION_HEADER(NewTypedObject) + + static MNewTypedObject *New(TempAllocator &alloc, + types::CompilerConstraintList *constraints, + InlineTypedObject *templateObject, + gc::InitialHeap initialHeap) + { + return new(alloc) MNewTypedObject(constraints, templateObject, initialHeap); + } + + InlineTypedObject *templateObject() const { + return templateObject_; + } + + gc::InitialHeap initialHeap() const { + return initialHeap_; + } + + virtual AliasSet getAliasSet() const { + return AliasSet::None(); + } +}; + class MTypedObjectProto : public MUnaryInstruction, public SingleObjectPolicy::Data diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index e62645939f4..5a8dcb17348 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -110,6 +110,7 @@ namespace jit { _(NewArray) \ _(NewArrayCopyOnWrite) \ _(NewObject) \ + _(NewTypedObject) \ _(NewDeclEnvObject) \ _(NewCallObject) \ _(NewRunOnceCallObject) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index dab075fccd8..f4cb82d21e3 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -202,6 +202,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor CUSTOM_OP(ToString) CUSTOM_OP(NewArray) UNSAFE_OP(NewArrayCopyOnWrite) + UNSAFE_OP(NewTypedObject) CUSTOM_OP(NewObject) CUSTOM_OP(NewCallObject) CUSTOM_OP(NewRunOnceCallObject) diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index e2700046bbd..5ed78b34c1d 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -17,6 +17,7 @@ namespace js { class DeclEnvObject; class ForkJoinContext; class StaticWithObject; +class InlineTypedObject; namespace jit { @@ -289,6 +290,7 @@ template struct TypeToDataType { /* Unexpected return type for a VMFunct template <> struct TypeToDataType { static const DataType result = Type_Bool; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; +template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; template <> struct TypeToDataType { static const DataType result = Type_Object; }; @@ -298,6 +300,7 @@ template <> struct TypeToDataType { static const DataType result = template <> struct TypeToDataType { static const DataType result = Type_Handle; }; template <> struct TypeToDataType { static const DataType result = Type_Handle; }; template <> struct TypeToDataType > { static const DataType result = Type_Handle; }; +template <> struct TypeToDataType > { static const DataType result = Type_Handle; }; template <> struct TypeToDataType > { static const DataType result = Type_Handle; }; template <> struct TypeToDataType > { static const DataType result = Type_Handle; }; template <> struct TypeToDataType > { static const DataType result = Type_Handle; }; @@ -328,6 +331,9 @@ template <> struct TypeToArgProperties { template <> struct TypeToArgProperties > { static const uint32_t result = TypeToArgProperties::result | VMFunction::ByRef; }; +template <> struct TypeToArgProperties > { + static const uint32_t result = TypeToArgProperties::result | VMFunction::ByRef; +}; template <> struct TypeToArgProperties > { static const uint32_t result = TypeToArgProperties::result | VMFunction::ByRef; }; @@ -396,6 +402,9 @@ template <> struct TypeToRootType { template <> struct TypeToRootType > { static const uint32_t result = VMFunction::RootObject; }; +template <> struct TypeToRootType > { + static const uint32_t result = VMFunction::RootObject; +}; template <> struct TypeToRootType > { static const uint32_t result = VMFunction::RootObject; }; From dfaece64aca2b1f423a1f69511eb754e2316c5aa Mon Sep 17 00:00:00 2001 From: Hannes Verschore Date: Tue, 4 Nov 2014 23:31:28 +0100 Subject: [PATCH 022/153] Bug 1052839 - Selfhost substr/slice/substring, r=waldo,till,jonco --- js/src/builtin/String.js | 120 +++++++++++++++ js/src/builtin/Utilities.js | 1 + js/src/jit/CodeGenerator.cpp | 104 +++++++++++++ js/src/jit/CodeGenerator.h | 1 + js/src/jit/IonBuilder.h | 1 + js/src/jit/LIR-Common.h | 30 ++++ js/src/jit/LOpcodes.h | 1 + js/src/jit/Lowering.cpp | 10 ++ js/src/jit/Lowering.h | 1 + js/src/jit/MCallOptimize.cpp | 34 +++++ js/src/jit/MIR.h | 41 +++++ js/src/jit/MOpcodes.h | 1 + js/src/jit/ParallelSafetyAnalysis.cpp | 1 + js/src/jscntxt.h | 1 + js/src/jsstr.cpp | 211 +++----------------------- js/src/jsstr.h | 11 +- js/src/vm/SelfHosting.cpp | 22 ++- js/src/vm/String.h | 3 + 18 files changed, 402 insertions(+), 192 deletions(-) diff --git a/js/src/builtin/String.js b/js/src/builtin/String.js index 577be1132b7..c8fa7bbc796 100644 --- a/js/src/builtin/String.js +++ b/js/src/builtin/String.js @@ -4,6 +4,126 @@ /*global intl_Collator: false, */ +/* ES6 Draft Oct 14, 2014 21.1.3.19 */ +function String_substring(start, end) { + // Steps 1-3. + CheckObjectCoercible(this); + var str = ToString(this); + + // Step 4. + var len = str.length; + + // Step 5. + var intStart = ToInteger(start); + + // Step 6. + var intEnd = (end === undefined) ? len : ToInteger(end); + + // Step 7. + var finalStart = std_Math_min(std_Math_max(intStart, 0), len); + + // Step 8. + var finalEnd = std_Math_min(std_Math_max(intEnd, 0), len); + + // Steps 9-10. + var from, to; + if (finalStart < finalEnd) { + from = finalStart; + to = finalEnd; + } else { + from = finalEnd; + to = finalStart; + } + + // Step 11. + // While |from| and |to - from| are bounded to the length of |str| and this + // and thus definitely in the int32 range, they can still be typed as + // double. Eagerly truncate since SubstringKernel only accepts int32. + return SubstringKernel(str, from | 0, (to - from) | 0); +} + +function String_static_substring(string, start, end) { + if (arguments.length < 1) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'String.substring'); + return callFunction(String_substring, string, start, end); +} + +/* ES6 Draft Oct 14, 2014 B.2.3.1 */ +function String_substr(start, length) { + // Steps 1-2. + CheckObjectCoercible(this); + var str = ToString(this); + + // Steps 3-4. + var intStart = ToInteger(start); + + // Steps 5-7. + var size = str.length; + // Use |size| instead of +Infinity to avoid performing calculations with + // doubles. (The result is the same either way.) + var end = (length === undefined) ? size : ToInteger(length); + + // Step 8. + if (intStart < 0) + intStart = std_Math_max(intStart + size, 0); + + // Step 9. + var resultLength = std_Math_min(std_Math_max(end, 0), size - intStart) + + // Step 10. + if (resultLength <= 0) + return ""; + + // Step 11. + // While |intStart| and |resultLength| are bounded to the length of |str| + // and thus definitely in the int32 range, they can still be typed as + // double. Eagerly truncate since SubstringKernel only accepts int32. + return SubstringKernel(str, intStart | 0, resultLength | 0); +} + +function String_static_substr(string, start, length) { + if (arguments.length < 1) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'String.substr'); + return callFunction(String_substr, string, start, length); +} + +/* ES6 Draft Oct 14, 2014 21.1.3.16 */ +function String_slice(start, end) { + // Steps 1-3. + CheckObjectCoercible(this); + var str = ToString(this); + + // Step 4. + var len = str.length; + + // Step 5. + var intStart = ToInteger(start); + + // Step 6. + var intEnd = (end === undefined) ? len : ToInteger(end); + + // Step 7. + var from = (intStart < 0) ? std_Math_max(len + intStart, 0) : std_Math_min(intStart, len); + + // Step 8. + var to = (intEnd < 0) ? std_Math_max(len + intEnd, 0) : std_Math_min(intEnd, len); + + // Step 9. + var span = std_Math_max(to - from, 0); + + // Step 10. + // While |from| and |span| are bounded to the length of |str| + // and thus definitely in the int32 range, they can still be typed as + // double. Eagerly truncate since SubstringKernel only accepts int32. + return SubstringKernel(str, from | 0, span | 0); +} + +function String_static_slice(string, start, end) { + if (arguments.length < 1) + ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'String.slice'); + return callFunction(String_slice, string, start, end); +} + /* ES6 Draft September 5, 2013 21.1.3.3 */ function String_codePointAt(pos) { // Steps 1-3. diff --git a/js/src/builtin/Utilities.js b/js/src/builtin/Utilities.js index 8425c12801e..bd1c36ace1d 100644 --- a/js/src/builtin/Utilities.js +++ b/js/src/builtin/Utilities.js @@ -32,6 +32,7 @@ // The few items below here are either self-hosted or installing them under a // std_Foo name would require ugly contortions, so they just get aliased here. var std_Array_indexOf = ArrayIndexOf; +var std_String_substring = String_substring; // WeakMap is a bare constructor without properties or methods. var std_WeakMap = WeakMap; // StopIteration is a bare constructor without properties or methods. diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 88c72fb731f..13f076f0e11 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -5968,6 +5968,110 @@ ConcatFatInlineString(MacroAssembler &masm, Register lhs, Register rhs, Register masm.ret(); } +typedef JSString *(*SubstringKernelFn)(JSContext *cx, HandleString str, int32_t begin, int32_t len); +static const VMFunction SubstringKernelInfo = + FunctionInfo(SubstringKernel); + +bool CodeGenerator::visitSubstr(LSubstr *lir) +{ + Register string = ToRegister(lir->string()); + Register begin = ToRegister(lir->begin()); + Register length = ToRegister(lir->length()); + Register output = ToRegister(lir->output()); + Register temp = ToRegister(lir->temp()); + Address stringFlags(string, JSString::offsetOfFlags()); + + Label isLatin1, notInline, nonZero, isInlinedLatin1; + + // For every edge case use the C++ variant. + // Note: we also use this upon allocation failure in newGCString and + // newGCFatInlineString. To squeeze out even more performance those failures + // can be handled by allocate in ool code and returning to jit code to fill + // in all data. + OutOfLineCode *ool = oolCallVM(SubstringKernelInfo, lir, + (ArgList(), string, begin, length), + StoreRegisterTo(output)); + if (!ool) + return false; + Label *slowPath = ool->entry(); + Label *done = ool->rejoin(); + + // Zero length, return emptystring. + masm.branchTest32(Assembler::NonZero, length, length, &nonZero); + const JSAtomState &names = GetIonContext()->runtime->names(); + masm.movePtr(ImmGCPtr(names.empty), output); + masm.jump(done); + + // Use slow path for ropes. + masm.bind(&nonZero); + static_assert(JSString::ROPE_FLAGS == 0, + "rope flags must be zero for (flags & TYPE_FLAGS_MASK) == 0 " + "to be a valid is-rope check"); + masm.branchTest32(Assembler::Zero, stringFlags, Imm32(JSString::TYPE_FLAGS_MASK), slowPath); + + // Handle inlined strings by creating a FatInlineString. + masm.branchTest32(Assembler::Zero, stringFlags, Imm32(JSString::INLINE_CHARS_BIT), ¬Inline); + masm.newGCFatInlineString(output, temp, slowPath); + masm.store32(length, Address(output, JSString::offsetOfLength())); + Address stringStorage(string, JSInlineString::offsetOfInlineStorage()); + Address outputStorage(output, JSInlineString::offsetOfInlineStorage()); + + masm.branchTest32(Assembler::NonZero, stringFlags, Imm32(JSString::LATIN1_CHARS_BIT), + &isInlinedLatin1); + { + masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS), + Address(output, JSString::offsetOfFlags())); + masm.computeEffectiveAddress(stringStorage, temp); + BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t))); + masm.computeEffectiveAddress(chars, begin); + masm.computeEffectiveAddress(outputStorage, temp); + CopyStringChars(masm, temp, begin, length, string, sizeof(char16_t), sizeof(char16_t)); + masm.store16(Imm32(0), Address(temp, 0)); + masm.jump(done); + } + masm.bind(&isInlinedLatin1); + { + masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS | JSString::LATIN1_CHARS_BIT), + Address(output, JSString::offsetOfFlags())); + masm.computeEffectiveAddress(stringStorage, temp); + static_assert(sizeof(char) == 1, "begin index shouldn't need scaling"); + masm.addPtr(temp, begin); + masm.computeEffectiveAddress(outputStorage, temp); + CopyStringChars(masm, temp, begin, length, string, sizeof(char), sizeof(char)); + masm.store8(Imm32(0), Address(temp, 0)); + masm.jump(done); + } + + // Handle other cases with a DependentString. + masm.bind(¬Inline); + masm.newGCString(output, temp, slowPath); + masm.store32(length, Address(output, JSString::offsetOfLength())); + masm.storePtr(string, Address(output, JSDependentString::offsetOfBase())); + + masm.branchTest32(Assembler::NonZero, stringFlags, Imm32(JSString::LATIN1_CHARS_BIT), &isLatin1); + { + masm.store32(Imm32(JSString::DEPENDENT_FLAGS), Address(output, JSString::offsetOfFlags())); + masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp); + BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t))); + masm.computeEffectiveAddress(chars, temp); + masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars())); + masm.jump(done); + } + masm.bind(&isLatin1); + { + masm.store32(Imm32(JSString::DEPENDENT_FLAGS | JSString::LATIN1_CHARS_BIT), + Address(output, JSString::offsetOfFlags())); + masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp); + static_assert(sizeof(char) == 1, "begin index shouldn't need scaling"); + masm.addPtr(begin, temp); + masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars())); + masm.jump(done); + } + + masm.bind(done); + return true; +} + JitCode * JitCompartment::generateStringConcatStub(JSContext *cx, ExecutionMode mode) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 4e24d799c61..ce3db386734 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -187,6 +187,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitTypedObjectProto(LTypedObjectProto *ins); bool visitTypedObjectUnsizedLength(LTypedObjectUnsizedLength *ins); bool visitStringLength(LStringLength *lir); + bool visitSubstr(LSubstr *lir); bool visitInitializedLength(LInitializedLength *lir); bool visitSetInitializedLength(LSetInitializedLength *lir); bool visitNotO(LNotO *ins); diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 6a7fc3027d0..277e780b8db 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -766,6 +766,7 @@ class IonBuilder const Class *clasp3 = nullptr, const Class *clasp4 = nullptr); InliningStatus inlineIsConstructing(CallInfo &callInfo); + InliningStatus inlineSubstringKernel(CallInfo &callInfo); // Testing functions. InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 686b1f792d6..10bf998cfdc 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -3410,6 +3410,36 @@ class LStringSplit : public LCallInstructionHelper<1, 2, 0> } }; +class LSubstr : public LInstructionHelper<1, 3, 1> +{ + public: + LIR_HEADER(Substr) + + LSubstr(const LAllocation &string, const LAllocation &begin, const LAllocation &length, + const LDefinition &temp) + { + setOperand(0, string); + setOperand(1, begin); + setOperand(2, length); + setTemp(0, temp); + } + const LAllocation *string() { + return getOperand(0); + } + const LAllocation *begin() { + return getOperand(1); + } + const LAllocation *length() { + return getOperand(2); + } + const LDefinition *temp() { + return getTemp(0); + } + const MStringSplit *mir() const { + return mir_->toStringSplit(); + } +}; + // Convert a 32-bit integer to a double. class LInt32ToDouble : public LInstructionHelper<1, 1, 0> { diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index c93166cf408..34ce62630ea 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -186,6 +186,7 @@ _(RegExpTest) \ _(RegExpReplace) \ _(StringReplace) \ + _(Substr) \ _(Lambda) \ _(LambdaArrow) \ _(LambdaForSingleton) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 8a21c7cc263..9c5fa6d7b1e 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2160,6 +2160,16 @@ LIRGenerator::visitStringReplace(MStringReplace *ins) return defineReturn(lir, ins) && assignSafepoint(lir, ins); } +bool +LIRGenerator::visitSubstr(MSubstr *ins) +{ + LSubstr *lir = new (alloc()) LSubstr(useFixed(ins->string(), CallTempReg1), + useRegister(ins->begin()), + useRegister(ins->length()), + temp()); + return define(lir, ins) && assignSafepoint(lir, ins); +} + bool LIRGenerator::visitLambda(MLambda *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 5b9620d230f..dd3667210e8 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -145,6 +145,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitCharCodeAt(MCharCodeAt *ins); bool visitFromCharCode(MFromCharCode *ins); bool visitStringSplit(MStringSplit *ins); + bool visitSubstr(MSubstr *ins); bool visitStart(MStart *start); bool visitOsrEntry(MOsrEntry *entry); bool visitNop(MNop *nop); diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 68bd67004b1..10064610fe4 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -197,6 +197,8 @@ IonBuilder::inlineNativeCall(CallInfo &callInfo, JSFunction *target) return inlineToString(callInfo); if (native == intrinsic_IsConstructing) return inlineIsConstructing(callInfo); + if (native == intrinsic_SubstringKernel) + return inlineSubstringKernel(callInfo); // TypedObject intrinsics. if (native == intrinsic_ObjectIsTypedObject) @@ -1550,6 +1552,38 @@ IonBuilder::inlineStrReplace(CallInfo &callInfo) return InliningStatus_Inlined; } +IonBuilder::InliningStatus +IonBuilder::inlineSubstringKernel(CallInfo &callInfo) +{ + MOZ_ASSERT(callInfo.argc() == 3); + MOZ_ASSERT(!callInfo.constructing()); + + // Return: String. + if (getInlineReturnType() != MIRType_String) + return InliningStatus_NotInlined; + + // Arg 0: String. + if (callInfo.getArg(0)->type() != MIRType_String) + return InliningStatus_NotInlined; + + // Arg 1: Int. + if (callInfo.getArg(1)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + + // Arg 2: Int. + if (callInfo.getArg(2)->type() != MIRType_Int32) + return InliningStatus_NotInlined; + + callInfo.setImplicitlyUsedUnchecked(); + + MSubstr *substr = MSubstr::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), + callInfo.getArg(2)); + current->add(substr); + current->push(substr); + + return InliningStatus_Inlined; +} + IonBuilder::InliningStatus IonBuilder::inlineUnsafePutElements(CallInfo &callInfo) { diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 16b3ddd6c36..e19cce4249c 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -6893,6 +6893,47 @@ class MStringReplace } }; +class MSubstr + : public MTernaryInstruction, + public Mix3Policy, IntPolicy<1>, IntPolicy<2>> +{ + private: + + MSubstr(MDefinition *string, MDefinition *begin, MDefinition *length) + : MTernaryInstruction(string, begin, length) + { + setResultType(MIRType_String); + } + + public: + INSTRUCTION_HEADER(Substr); + + static MSubstr *New(TempAllocator &alloc, MDefinition *string, MDefinition *begin, + MDefinition *length) + { + return new(alloc) MSubstr(string, begin, length); + } + + MDefinition *string() { + return getOperand(0); + } + + MDefinition *begin() { + return getOperand(1); + } + + MDefinition *length() { + return getOperand(2); + } + + bool congruentTo(const MDefinition *ins) const { + return congruentIfOperandsEqual(ins); + } + AliasSet getAliasSet() const { + return AliasSet::None(); + } +}; + struct LambdaFunctionInfo { // The functions used in lambdas are the canonical original function in diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 5a8dcb17348..3d5ea4cad87 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -95,6 +95,7 @@ namespace jit { _(CharCodeAt) \ _(FromCharCode) \ _(StringSplit) \ + _(Substr) \ _(Return) \ _(Throw) \ _(Box) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index f4cb82d21e3..96d335dbb35 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -321,6 +321,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor UNSAFE_OP(CallInstanceOf) UNSAFE_OP(ProfilerStackOp) UNSAFE_OP(GuardString) + UNSAFE_OP(Substr) UNSAFE_OP(NewDeclEnvObject) UNSAFE_OP(In) UNSAFE_OP(InArray) diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 91e1dd45f4b..eb47e1df753 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -977,6 +977,7 @@ bool intrinsic_IsCallable(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_NewDenseArray(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_IsConstructing(JSContext *cx, unsigned argc, Value *vp); +bool intrinsic_SubstringKernel(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_UnsafePutElements(JSContext *cx, unsigned argc, Value *vp); bool intrinsic_DefineDataProperty(JSContext *cx, unsigned argc, Value *vp); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index c74f06e6a0a..e16bc251d85 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -575,9 +575,17 @@ ValueToIntegerRange(JSContext *cx, HandleValue v, int32_t *out) return true; } -static JSString * -DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) +JSString * +js::SubstringKernel(JSContext *cx, HandleString str, int32_t beginInt, int32_t lengthInt) { + MOZ_ASSERT(0 <= beginInt); + MOZ_ASSERT(0 <= lengthInt); + MOZ_ASSERT(beginInt <= str->length()); + MOZ_ASSERT(lengthInt <= str->length() - beginInt); + + uint32_t begin = beginInt; + uint32_t len = lengthInt; + /* * Optimization for one level deep ropes. * This is common for the following pattern: @@ -591,16 +599,13 @@ DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) JSRope *rope = &str->asRope(); /* Substring is totally in leftChild of rope. */ - if (begin + len <= rope->leftChild()->length()) { - str = rope->leftChild(); - return NewDependentString(cx, str, begin, len); - } + if (begin + len <= rope->leftChild()->length()) + return NewDependentString(cx, rope->leftChild(), begin, len); /* Substring is totally in rightChild of rope. */ if (begin >= rope->leftChild()->length()) { - str = rope->rightChild(); begin -= rope->leftChild()->length(); - return NewDependentString(cx, str, begin, len); + return NewDependentString(cx, rope->rightChild(), begin, len); } /* @@ -628,65 +633,6 @@ DoSubstr(JSContext *cx, JSString *str, size_t begin, size_t len) return NewDependentString(cx, str, begin, len); } -bool -js::str_substring(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - JSString *str = ThisToStringForStringProto(cx, args); - if (!str) - return false; - - int32_t length, begin, end; - if (args.length() > 0) { - end = length = int32_t(str->length()); - - if (args[0].isInt32()) { - begin = args[0].toInt32(); - } else { - RootedString strRoot(cx, str); - if (!ValueToIntegerRange(cx, args[0], &begin)) - return false; - str = strRoot; - } - - if (begin < 0) - begin = 0; - else if (begin > length) - begin = length; - - if (args.hasDefined(1)) { - if (args[1].isInt32()) { - end = args[1].toInt32(); - } else { - RootedString strRoot(cx, str); - if (!ValueToIntegerRange(cx, args[1], &end)) - return false; - str = strRoot; - } - - if (end > length) { - end = length; - } else { - if (end < 0) - end = 0; - if (end < begin) { - int32_t tmp = begin; - begin = end; - end = tmp; - } - } - } - - str = DoSubstr(cx, str, size_t(begin), size_t(end - begin)); - if (!str) - return false; - } - - args.rval().setString(str); - return true; -} - template static JSString * ToLowerCase(JSContext *cx, JSLinearString *str) @@ -3992,54 +3938,6 @@ js::str_split_string(JSContext *cx, HandleTypeObject type, HandleString str, Han return aobj; } -static bool -str_substr(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - RootedString str(cx, ThisToStringForStringProto(cx, args)); - if (!str) - return false; - - int32_t length, len, begin; - if (args.length() > 0) { - length = int32_t(str->length()); - if (!ValueToIntegerRange(cx, args[0], &begin)) - return false; - - if (begin >= length) { - args.rval().setString(cx->runtime()->emptyString); - return true; - } - if (begin < 0) { - begin += length; /* length + INT_MIN will always be less than 0 */ - if (begin < 0) - begin = 0; - } - - if (args.hasDefined(1)) { - if (!ValueToIntegerRange(cx, args[1], &len)) - return false; - - if (len <= 0) { - args.rval().setString(cx->runtime()->emptyString); - return true; - } - - if (uint32_t(length) < uint32_t(begin + len)) - len = length - begin; - } else { - len = length - begin; - } - - str = DoSubstr(cx, str, size_t(begin), size_t(len)); - if (!str) - return false; - } - - args.rval().setString(str); - return true; -} - /* * Python-esque sequence operations. */ @@ -4076,73 +3974,6 @@ str_concat(JSContext *cx, unsigned argc, Value *vp) return true; } -static bool -str_slice(JSContext *cx, unsigned argc, Value *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - if (args.length() == 1 && args.thisv().isString() && args[0].isInt32()) { - JSString *str = args.thisv().toString(); - size_t begin = args[0].toInt32(); - size_t end = str->length(); - if (begin <= end) { - size_t length = end - begin; - if (length == 0) { - str = cx->runtime()->emptyString; - } else { - str = (length == 1) - ? cx->staticStrings().getUnitStringForElement(cx, str, begin) - : NewDependentString(cx, str, begin, length); - if (!str) - return false; - } - args.rval().setString(str); - return true; - } - } - - RootedString str(cx, ThisToStringForStringProto(cx, args)); - if (!str) - return false; - - if (args.length() != 0) { - double begin, end, length; - - if (!ToInteger(cx, args[0], &begin)) - return false; - length = str->length(); - if (begin < 0) { - begin += length; - if (begin < 0) - begin = 0; - } else if (begin > length) { - begin = length; - } - - if (args.hasDefined(1)) { - if (!ToInteger(cx, args[1], &end)) - return false; - if (end < 0) { - end += length; - if (end < 0) - end = 0; - } else if (end > length) { - end = length; - } - if (end < begin) - end = begin; - } else { - end = length; - } - - str = NewDependentString(cx, str, size_t(begin), size_t(end - begin)); - if (!str) - return false; - } - args.rval().setString(str); - return true; -} - static const JSFunctionSpec string_methods[] = { #if JS_HAS_TOSOURCE JS_FN("quote", str_quote, 0,JSFUN_GENERIC_NATIVE), @@ -4152,11 +3983,11 @@ static const JSFunctionSpec string_methods[] = { /* Java-like methods. */ JS_FN(js_toString_str, js_str_toString, 0,0), JS_FN(js_valueOf_str, js_str_toString, 0,0), - JS_FN("substring", str_substring, 2,JSFUN_GENERIC_NATIVE), JS_FN("toLowerCase", str_toLowerCase, 0,JSFUN_GENERIC_NATIVE), JS_FN("toUpperCase", str_toUpperCase, 0,JSFUN_GENERIC_NATIVE), JS_FN("charAt", js_str_charAt, 1,JSFUN_GENERIC_NATIVE), JS_FN("charCodeAt", js_str_charCodeAt, 1,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("substring", "String_substring", 2,0), JS_SELF_HOSTED_FN("codePointAt", "String_codePointAt", 1,0), JS_FN("contains", str_contains, 1,JSFUN_GENERIC_NATIVE), JS_FN("indexOf", str_indexOf, 1,JSFUN_GENERIC_NATIVE), @@ -4183,11 +4014,11 @@ static const JSFunctionSpec string_methods[] = { JS_FN("search", str_search, 1,JSFUN_GENERIC_NATIVE), JS_FN("replace", str_replace, 2,JSFUN_GENERIC_NATIVE), JS_FN("split", str_split, 2,JSFUN_GENERIC_NATIVE), - JS_FN("substr", str_substr, 2,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("substr", "String_substr", 2,0), /* Python-esque sequence methods. */ JS_FN("concat", str_concat, 1,JSFUN_GENERIC_NATIVE), - JS_FN("slice", str_slice, 2,JSFUN_GENERIC_NATIVE), + JS_SELF_HOSTED_FN("slice", "String_slice", 2,0), /* HTML string methods. */ JS_SELF_HOSTED_FN("bold", "String_bold", 0,0), @@ -4293,13 +4124,17 @@ js::str_fromCharCode_one_arg(JSContext *cx, HandleValue code, MutableHandleValue static const JSFunctionSpec string_static_methods[] = { JS_FN("fromCharCode", js::str_fromCharCode, 1, 0), - JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 1, 0), - JS_SELF_HOSTED_FN("raw", "String_static_raw", 2, 0), + + JS_SELF_HOSTED_FN("fromCodePoint", "String_static_fromCodePoint", 1,0), + JS_SELF_HOSTED_FN("raw", "String_static_raw", 2,0), + JS_SELF_HOSTED_FN("substring", "String_static_substring", 3,0), + JS_SELF_HOSTED_FN("substr", "String_static_substr", 3,0), + JS_SELF_HOSTED_FN("slice", "String_static_slice", 3,0), // This must be at the end because of bug 853075: functions listed after // self-hosted methods aren't available in self-hosted code. #if EXPOSE_INTL_API - JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0), + JS_SELF_HOSTED_FN("localeCompare", "String_static_localeCompare", 2,0), #endif JS_FS_END }; diff --git a/js/src/jsstr.h b/js/src/jsstr.h index 5231ab13369..e262ace82aa 100644 --- a/js/src/jsstr.h +++ b/js/src/jsstr.h @@ -249,6 +249,14 @@ EqualChars(const Char1 *s1, const Char2 *s2, size_t len) return true; } +/* + * Computes |str|'s substring for the range [beginInt, beginInt + lengthInt). + * Negative, overlarge, swapped, etc. |beginInt| and |lengthInt| are forbidden + * and constitute API misuse. + */ +JSString * +SubstringKernel(JSContext *cx, HandleString str, int32_t beginInt, int32_t lengthInt); + /* * Inflate bytes in ASCII encoding to char16_t code units. Return null on error, * otherwise return the char16_t buffer that was malloc'ed. length is updated to @@ -311,9 +319,6 @@ str_lastIndexOf(JSContext *cx, unsigned argc, Value *vp); extern bool str_startsWith(JSContext *cx, unsigned argc, Value *vp); -extern bool -str_substring(JSContext *cx, unsigned argc, Value *vp); - extern bool str_toLowerCase(JSContext *cx, unsigned argc, Value *vp); diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index 1be699b9659..17ef9e321f1 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -120,6 +120,26 @@ intrinsic_IsConstructor(JSContext *cx, unsigned argc, Value *vp) return true; } +bool +js::intrinsic_SubstringKernel(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args[0].isString()); + MOZ_ASSERT(args[1].isInt32()); + MOZ_ASSERT(args[2].isInt32()); + + RootedString str(cx, args[0].toString()); + int32_t begin = args[1].toInt32(); + int32_t length = args[2].toInt32(); + + JSString *substr = SubstringKernel(cx, str, begin, length); + if (!substr) + return false; + + args.rval().setString(substr); + return true; +} + static bool intrinsic_OwnPropertyKeys(JSContext *cx, unsigned argc, Value *vp) { @@ -1016,7 +1036,6 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_String_replace", str_replace, 2,0), JS_FN("std_String_split", str_split, 2,0), JS_FN("std_String_startsWith", str_startsWith, 1,0), - JS_FN("std_String_substring", str_substring, 2,0), JS_FN("std_String_toLowerCase", str_toLowerCase, 0,0), JS_FN("std_String_toUpperCase", str_toUpperCase, 0,0), @@ -1041,6 +1060,7 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("_IsConstructing", intrinsic_IsConstructing, 0,0), JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0), JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0), + JS_FN("SubstringKernel", intrinsic_SubstringKernel, 3,0), JS_FN("UnsafePutElements", intrinsic_UnsafePutElements, 3,0), JS_FN("_DefineDataProperty", intrinsic_DefineDataProperty, 4,0), diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 5cd432afef6..5f134309050 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -475,6 +475,9 @@ class JSString : public js::gc::TenuredCell } static size_t offsetOfNonInlineChars() { + static_assert(offsetof(JSString, d.s.u2.nonInlineCharsTwoByte) == + offsetof(JSString, d.s.u2.nonInlineCharsLatin1), + "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset"); return offsetof(JSString, d.s.u2.nonInlineCharsTwoByte); } From 655a37a8b592e16f7c341506d3f8888ca3f9a4eb Mon Sep 17 00:00:00 2001 From: Benoit Jacob Date: Tue, 4 Nov 2014 18:17:11 -0500 Subject: [PATCH 023/153] Bug 1021265 - Add AppNotes about blacklisting DisplayLink - r=jrmuizel --- gfx/thebes/gfxWindowsPlatform.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/gfx/thebes/gfxWindowsPlatform.cpp b/gfx/thebes/gfxWindowsPlatform.cpp index 8c44ecd92a8..5692dc7d586 100644 --- a/gfx/thebes/gfxWindowsPlatform.cpp +++ b/gfx/thebes/gfxWindowsPlatform.cpp @@ -70,6 +70,10 @@ #include "SurfaceCache.h" #include "gfxPrefs.h" +#if defined(MOZ_CRASHREPORTER) +#include "nsExceptionHandler.h" +#endif + using namespace mozilla; using namespace mozilla::gfx; using namespace mozilla::layers; @@ -1522,9 +1526,15 @@ bool DoesD3D11DeviceWork(ID3D11Device *device) gfxWindowsPlatform::GetDLLVersion(L"dlumd32.dll", displayLinkModuleVersionString); uint64_t displayLinkModuleVersion; if (!ParseDriverVersion(displayLinkModuleVersionString, &displayLinkModuleVersion)) { +#if defined(MOZ_CRASHREPORTER) + CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DisplayLink: could not parse version\n")); +#endif return false; } if (displayLinkModuleVersion <= GFX_DRIVER_VERSION(8,6,1,36484)) { +#if defined(MOZ_CRASHREPORTER) + CrashReporter::AppendAppNotesToCrashReport(NS_LITERAL_CSTRING("DisplayLink: too old version\n")); +#endif return false; } } From f8116d592ab662f66458d4e4618c1e117b8354fd Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 30 Oct 2014 19:48:10 -0700 Subject: [PATCH 024/153] Bug 1091986 (part 1) - Remove an over-zealous assertion in Vector.h. --- mfbt/Vector.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/mfbt/Vector.h b/mfbt/Vector.h index 36cb70c00cd..a258227a7e7 100644 --- a/mfbt/Vector.h +++ b/mfbt/Vector.h @@ -724,8 +724,6 @@ MOZ_NEVER_INLINE bool VectorBase::growStorageBy(size_t aIncr) { MOZ_ASSERT(mLength + aIncr > mCapacity); - MOZ_ASSERT_IF(!usingInlineStorage(), - !detail::CapacityHasExcessSpace(mCapacity)); /* * When choosing a new capacity, its size should is as close to 2**N bytes From b7aca515ce1552e7e393175f594de4542b949723 Mon Sep 17 00:00:00 2001 From: Nicholas Nethercote Date: Thu, 30 Oct 2014 19:48:30 -0700 Subject: [PATCH 025/153] Bug 1091986 (part 2) - Change nsStreamLoader::mData to a mozilla::Vector. r=mcmanus. --- netwerk/base/src/nsStreamLoader.cpp | 60 ++++++++++------------------- netwerk/base/src/nsStreamLoader.h | 9 ++--- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/netwerk/base/src/nsStreamLoader.cpp b/netwerk/base/src/nsStreamLoader.cpp index 245b91524e1..5d9cc774523 100644 --- a/netwerk/base/src/nsStreamLoader.cpp +++ b/netwerk/base/src/nsStreamLoader.cpp @@ -9,16 +9,15 @@ #include "nsError.h" #include "GeckoProfiler.h" +#include + nsStreamLoader::nsStreamLoader() - : mData(nullptr), - mAllocated(0), - mLength(0) + : mData() { } nsStreamLoader::~nsStreamLoader() { - ReleaseData(); } NS_IMETHODIMP @@ -49,7 +48,7 @@ NS_IMPL_ISUPPORTS(nsStreamLoader, nsIStreamLoader, NS_IMETHODIMP nsStreamLoader::GetNumBytesRead(uint32_t* aNumBytes) { - *aNumBytes = mLength; + *aNumBytes = mData.length(); return NS_OK; } @@ -61,7 +60,7 @@ nsStreamLoader::GetRequest(nsIRequest **aRequest) return NS_OK; } -NS_IMETHODIMP +NS_IMETHODIMP nsStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt) { nsCOMPtr chan( do_QueryInterface(request) ); @@ -69,25 +68,21 @@ nsStreamLoader::OnStartRequest(nsIRequest* request, nsISupports *ctxt) int64_t contentLength = -1; chan->GetContentLength(&contentLength); if (contentLength >= 0) { - if (contentLength > UINT32_MAX) { - // Too big to fit into uint32, so let's bail. - // XXX we should really make mAllocated and mLength 64-bit instead. + if (uint64_t(contentLength) > std::numeric_limits::max()) { + // Too big to fit into size_t, so let's bail. return NS_ERROR_OUT_OF_MEMORY; } - uint32_t contentLength32 = uint32_t(contentLength); // preallocate buffer - mData = static_cast(moz_malloc(contentLength32)); - if (!mData) { + if (!mData.initCapacity(contentLength)) { return NS_ERROR_OUT_OF_MEMORY; } - mAllocated = contentLength32; } } mContext = ctxt; return NS_OK; } -NS_IMETHODIMP +NS_IMETHODIMP nsStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt, nsresult aStatus) { @@ -97,12 +92,14 @@ nsStreamLoader::OnStopRequest(nsIRequest* request, nsISupports *ctxt, if (mObserver) { // provide nsIStreamLoader::request during call to OnStreamComplete mRequest = request; + size_t length = mData.length(); + uint8_t* elems = mData.extractRawBuffer(); nsresult rv = mObserver->OnStreamComplete(this, mContext, aStatus, - mLength, mData); - if (rv == NS_SUCCESS_ADOPTED_DATA) { - // the observer now owns the data buffer, and the loader must - // not deallocate it - mData = nullptr; + length, elems); + if (rv != NS_SUCCESS_ADOPTED_DATA) { + // The observer didn't take ownership of the extracted data buffer, so + // put it back into mData. + mData.replaceRawBuffer(elems, length); } // done.. cleanup ReleaseData(); @@ -123,23 +120,11 @@ nsStreamLoader::WriteSegmentFun(nsIInputStream *inStr, { nsStreamLoader *self = (nsStreamLoader *) closure; - if (count > UINT32_MAX - self->mLength) { - return NS_ERROR_ILLEGAL_VALUE; // is there a better error to use here? + if (!self->mData.append(fromSegment, count)) { + self->mData.clearAndFree(); + return NS_ERROR_OUT_OF_MEMORY; } - if (self->mLength + count > self->mAllocated) { - self->mData = static_cast(moz_realloc(self->mData, - self->mLength + count)); - if (!self->mData) { - self->ReleaseData(); - return NS_ERROR_OUT_OF_MEMORY; - } - self->mAllocated = self->mLength + count; - } - - ::memcpy(self->mData + self->mLength, fromSegment, count); - self->mLength += count; - *writeCount = count; return NS_OK; @@ -157,10 +142,5 @@ nsStreamLoader::OnDataAvailable(nsIRequest* request, nsISupports *ctxt, void nsStreamLoader::ReleaseData() { - if (mData) { - moz_free(mData); - mData = nullptr; - } - mLength = 0; - mAllocated = 0; + mData.clearAndFree(); } diff --git a/netwerk/base/src/nsStreamLoader.h b/netwerk/base/src/nsStreamLoader.h index a42116c75da..791bc5375a3 100644 --- a/netwerk/base/src/nsStreamLoader.h +++ b/netwerk/base/src/nsStreamLoader.h @@ -9,6 +9,7 @@ #include "nsIStreamLoader.h" #include "nsCOMPtr.h" #include "mozilla/Attributes.h" +#include "mozilla/Vector.h" class nsIRequest; @@ -39,11 +40,9 @@ protected: nsCOMPtr mContext; // the observer's context nsCOMPtr mRequest; - uint8_t *mData; // buffer to accumulate incoming data - uint32_t mAllocated; // allocated size of data buffer (we preallocate if - // contentSize is available) - uint32_t mLength; // actual length of data in buffer - // (must be <= mAllocated) + // Buffer to accumulate incoming data. We preallocate if contentSize is + // available. + mozilla::Vector mData; }; #endif // nsStreamLoader_h__ From 63b2f474490964f71c20652bb4ce09907fdc6ea8 Mon Sep 17 00:00:00 2001 From: Wes Kocher Date: Tue, 4 Nov 2014 16:39:12 -0800 Subject: [PATCH 026/153] Backed out 3 changesets (bug 1088831) for mochitest-2 failures --- js/src/gc/Statistics.cpp | 77 +++------------------------------------ js/src/gc/Statistics.h | 25 ++----------- js/src/gc/StoreBuffer.cpp | 25 +++---------- js/src/gc/StoreBuffer.h | 8 +--- js/src/gc/Verifier.cpp | 5 +-- js/src/jsgc.cpp | 2 - js/src/shell/js.cpp | 45 ----------------------- 7 files changed, 15 insertions(+), 172 deletions(-) diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index bc897c8bc46..af9602d1a23 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -27,7 +27,6 @@ using namespace js::gc; using namespace js::gcstats; using mozilla::PodArrayZero; -using mozilla::PodZero; /* Except for the first and last, slices of less than 10ms are not reported. */ static const int64_t SLICE_MIN_REPORT_TIME = 10 * PRMJ_USEC_PER_MSEC; @@ -284,7 +283,6 @@ struct PhaseInfo static const Phase PHASE_NO_PARENT = PHASE_LIMIT; static const PhaseInfo phases[] = { - { PHASE_MUTATOR, "Mutator Running", PHASE_NO_PARENT }, { PHASE_GC_BEGIN, "Begin Callback", PHASE_NO_PARENT }, { PHASE_WAIT_BACKGROUND_THREAD, "Wait Background Thread", PHASE_NO_PARENT }, { PHASE_MARK_DISCARD_CODE, "Mark Discard Code", PHASE_NO_PARENT }, @@ -328,9 +326,6 @@ static const PhaseInfo phases[] = { { PHASE_COMPACT_UPDATE, "Compact Update", PHASE_COMPACT, }, { PHASE_COMPACT_UPDATE_GRAY, "Compact Update Gray", PHASE_COMPACT_UPDATE, }, { PHASE_GC_END, "End Callback", PHASE_NO_PARENT }, - { PHASE_MINOR_GC, "Minor GC", PHASE_NO_PARENT }, - { PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC, "Compact Store Buffers", PHASE_MINOR_GC }, - { PHASE_COMPACT_STOREBUFFER_NO_PARENT, "Compact Store Buffers (toplevel)", PHASE_NO_PARENT }, { PHASE_LIMIT, nullptr, PHASE_NO_PARENT } }; @@ -390,8 +385,6 @@ Statistics::formatData(StatisticsSerializer &ss, uint64_t timestamp) ss.appendNumber("Total Zones", "%d", "", zoneStats.zoneCount); ss.appendNumber("Total Compartments", "%d", "", zoneStats.compartmentCount); ss.appendNumber("Minor GCs", "%d", "", counts[STAT_MINOR_GC]); - ss.appendNumber("Store Buffer Compactions", "%d", "", counts[STAT_COMPACT_STOREBUFFER]); - ss.appendNumber("Store Buffer Overflows", "%d", "", counts[STAT_STOREBUFFER_OVERFLOW]); ss.appendNumber("MMU (20ms)", "%d", "%", int(mmu20 * 100)); ss.appendNumber("MMU (50ms)", "%d", "%", int(mmu50 * 100)); ss.appendDecimal("SCC Sweep Total", "ms", t(sccTotal)); @@ -483,8 +476,6 @@ Statistics::formatDescription() Zones Collected: %d of %d\n\ Compartments Collected: %d of %d\n\ MinorGCs since last GC: %d\n\ - Store Buffer Compactions: %d\n\ - Store Buffer Overflows: %d\n\ MMU 20ms:%.1f%%; 50ms:%.1f%%\n\ SCC Sweep Total (MaxPause): %.3fms (%.3fms)\n\ HeapSize: %.3f MiB\n\ @@ -500,8 +491,6 @@ Statistics::formatDescription() zoneStats.collectedZoneCount, zoneStats.zoneCount, zoneStats.collectedCompartmentCount, zoneStats.compartmentCount, counts[STAT_MINOR_GC], - counts[STAT_COMPACT_STOREBUFFER], - counts[STAT_STOREBUFFER_OVERFLOW], mmu20 * 100., mmu50 * 100., t(sccTotal), t(sccLongest), double(preBytes) / 1024. / 1024., @@ -644,8 +633,6 @@ Statistics::Statistics(JSRuntime *rt) fullFormat(false), gcDepth(0), nonincrementalReason(nullptr), - timingMutator(false), - timedGCStart(0), preBytes(0), maxPauseInInterval(0), phaseNestingDepth(0), @@ -653,8 +640,6 @@ Statistics::Statistics(JSRuntime *rt) { PodArrayZero(phaseTotals); PodArrayZero(counts); - PodArrayZero(phaseStartTimes); - PodArrayZero(phaseTimes); char *env = getenv("MOZ_GCTIMER"); if (!env || strcmp(env, "none") == 0) { @@ -738,6 +723,9 @@ Statistics::printStats() void Statistics::beginGC(JSGCInvocationKind kind) { + PodArrayZero(phaseStartTimes); + PodArrayZero(phaseTimes); + slices.clearAndFree(); sccTimes.clearAndFree(); gckind = kind; @@ -779,11 +767,6 @@ Statistics::endGC() if (fp) printStats(); - - // Clear the timers at the end of a GC because we accumulate time for some - // phases (eg storebuffer compaction) during the mutator's run. - PodZero(&phaseStartTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN); - PodZero(&phaseTimes[PHASE_GC_BEGIN], PHASE_LIMIT - PHASE_GC_BEGIN); } void @@ -840,58 +823,17 @@ Statistics::endSlice() PodArrayZero(counts); } -void -Statistics::startTimingMutator() -{ - MOZ_ASSERT(!timingMutator); - - // Should only be called from outside of GC - MOZ_ASSERT(phaseNestingDepth == 0); - - timingMutator = true; - timedGCTime = 0; - phaseStartTimes[PHASE_MUTATOR] = 0; - phaseTimes[PHASE_MUTATOR] = 0; - timedGCStart = 0; - - beginPhase(PHASE_MUTATOR); -} - -void -Statistics::stopTimingMutator(double &mutator_ms, double &gc_ms) -{ - MOZ_ASSERT(timingMutator); - - // Should only be called from outside of GC - MOZ_ASSERT(phaseNestingDepth == 1 && phaseNesting[0] == PHASE_MUTATOR); - - endPhase(PHASE_MUTATOR); - mutator_ms = t(phaseTimes[PHASE_MUTATOR]); - gc_ms = t(timedGCTime); - timingMutator = false; -} - void Statistics::beginPhase(Phase phase) { /* Guard against re-entry */ MOZ_ASSERT(!phaseStartTimes[phase]); - if (timingMutator) { - if (phaseNestingDepth == 1 && phaseNesting[0] == PHASE_MUTATOR) { - endPhase(PHASE_MUTATOR); - timedGCStart = PRMJ_Now(); - } - } - #ifdef DEBUG MOZ_ASSERT(phases[phase].index == phase); Phase parent = phaseNestingDepth ? phaseNesting[phaseNestingDepth - 1] : PHASE_NO_PARENT; MOZ_ASSERT(phaseNestingDepth < MAX_NESTING); MOZ_ASSERT_IF(gcDepth == 1, phases[phase].parent == parent); - MOZ_ASSERT_IF(phase == PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC, parent == PHASE_MINOR_GC); - MOZ_ASSERT_IF(phase == PHASE_COMPACT_STOREBUFFER_NO_PARENT, parent == PHASE_NO_PARENT); - phaseNesting[phaseNestingDepth] = phase; phaseNestingDepth++; #endif @@ -904,19 +846,10 @@ Statistics::endPhase(Phase phase) { phaseNestingDepth--; - int64_t now = PRMJ_Now(); - int64_t t = now - phaseStartTimes[phase]; - if (!slices.empty()) - slices.back().phaseTimes[phase] += t; + int64_t t = PRMJ_Now() - phaseStartTimes[phase]; + slices.back().phaseTimes[phase] += t; phaseTimes[phase] += t; phaseStartTimes[phase] = 0; - - if (timingMutator) { - if (phaseNestingDepth == 0 && phase != PHASE_MUTATOR) { - timedGCTime += now - timedGCStart; - beginPhase(PHASE_MUTATOR); - } - } } void diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index cffc33a2b58..933d449aefe 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -27,7 +27,6 @@ class GCParallelTask; namespace gcstats { enum Phase { - PHASE_MUTATOR, PHASE_GC_BEGIN, PHASE_WAIT_BACKGROUND_THREAD, PHASE_MARK_DISCARD_CODE, @@ -71,9 +70,6 @@ enum Phase { PHASE_COMPACT_UPDATE, PHASE_COMPACT_UPDATE_GRAY, PHASE_GC_END, - PHASE_MINOR_GC, - PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC, - PHASE_COMPACT_STOREBUFFER_NO_PARENT, PHASE_LIMIT }; @@ -83,13 +79,6 @@ enum Stat { STAT_DESTROY_CHUNK, STAT_MINOR_GC, - // Number of times the storebuffers were compacted - STAT_COMPACT_STOREBUFFER, - - // Number of times a 'put' into a storebuffer overflowed, triggering a - // compaction - STAT_STOREBUFFER_OVERFLOW, - STAT_LIMIT }; @@ -129,9 +118,6 @@ struct Statistics JS::gcreason::Reason reason); void endSlice(); - void startTimingMutator(); - void stopTimingMutator(double &mutator_ms, double &gc_ms); - void reset(const char *reason) { slices.back().resetReason = reason; } void nonincremental(const char *reason) { nonincrementalReason = reason; } @@ -193,13 +179,6 @@ struct Statistics /* Most recent time when the given phase started. */ int64_t phaseStartTimes[PHASE_LIMIT]; - /* Are we currently timing mutator vs GC time? */ - bool timingMutator; - - /* Bookkeeping for GC timings when timingMutator is true */ - int64_t timedGCStart; - int64_t timedGCTime; - /* Total time in a given phase for this GC. */ int64_t phaseTimes[PHASE_LIMIT]; @@ -215,10 +194,12 @@ struct Statistics /* Records the maximum GC pause in an API-controlled interval (in us). */ int64_t maxPauseInInterval; +#ifdef DEBUG /* Phases that are currently on stack. */ static const size_t MAX_NESTING = 8; Phase phaseNesting[MAX_NESTING]; - size_t phaseNestingDepth; +#endif + mozilla::DebugOnly phaseNestingDepth; /* Sweep times for SCCs of compartments. */ Vector sccTimes; diff --git a/js/src/gc/StoreBuffer.cpp b/js/src/gc/StoreBuffer.cpp index 23efde94426..0587e434f03 100644 --- a/js/src/gc/StoreBuffer.cpp +++ b/js/src/gc/StoreBuffer.cpp @@ -10,7 +10,6 @@ #include "mozilla/Assertions.h" -#include "gc/Statistics.h" #include "vm/ArgumentsObject.h" #include "vm/ForkJoin.h" @@ -20,12 +19,6 @@ using namespace js; using namespace js::gc; using mozilla::ReentrancyGuard; -gcstats::Statistics& -StoreBuffer::stats() -{ - return runtime_->gc.stats; -} - /*** Edges ***/ void @@ -100,8 +93,6 @@ StoreBuffer::MonoTypeBuffer::handleOverflow(StoreBuffer *owner) * Compact the buffer now, and if that fails to free enough space then * trigger a minor collection. */ - gcstats::AutoPhase ap(owner->stats(), gcstats::PHASE_COMPACT_STOREBUFFER_NO_PARENT); - owner->stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW); compact(owner); if (isLowOnSpace()) owner->setAboutToOverflow(); @@ -110,10 +101,8 @@ StoreBuffer::MonoTypeBuffer::handleOverflow(StoreBuffer *owner) * A minor GC has already been triggered, so there's no point * compacting unless the buffer is totally full. */ - if (storage_->availableInCurrentChunk() < sizeof(T)) { - owner->stats().count(gcstats::STAT_STOREBUFFER_OVERFLOW); - maybeCompact(owner, gcstats::PHASE_COMPACT_STOREBUFFER_NO_PARENT); - } + if (storage_->availableInCurrentChunk() < sizeof(T)) + maybeCompact(owner); } } @@ -141,7 +130,6 @@ StoreBuffer::MonoTypeBuffer::compactRemoveDuplicates(StoreBuffer *owner) storage_->release(insert.mark()); duplicates.clear(); - owner->stats().count(gcstats::STAT_COMPACT_STOREBUFFER); } template @@ -155,13 +143,11 @@ StoreBuffer::MonoTypeBuffer::compact(StoreBuffer *owner) template void -StoreBuffer::MonoTypeBuffer::maybeCompact(StoreBuffer *owner, int phase) +StoreBuffer::MonoTypeBuffer::maybeCompact(StoreBuffer *owner) { MOZ_ASSERT(storage_); - if (storage_->used() != usedAtLastCompact_) { - gcstats::AutoPhase ap(owner->stats(), static_cast(phase)); + if (storage_->used() != usedAtLastCompact_) compact(owner); - } } template @@ -173,8 +159,7 @@ StoreBuffer::MonoTypeBuffer::mark(StoreBuffer *owner, JSTracer *trc) if (!storage_) return; - maybeCompact(owner, gcstats::PHASE_COMPACT_STOREBUFFER_IN_MINOR_GC); - + maybeCompact(owner); for (LifoAlloc::Enum e(*storage_); !e.empty(); e.popFront()) { T *edge = e.get(); edge->mark(trc); diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h index 09276e8665a..b5b19fb6345 100644 --- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -22,10 +22,6 @@ namespace js { -namespace gcstats { -struct Statistics; -} - MOZ_NORETURN void CrashAtUnhandlableOOM(const char *reason); @@ -144,7 +140,7 @@ class StoreBuffer virtual void compact(StoreBuffer *owner); /* Compacts if any entries have been added since the last compaction. */ - void maybeCompact(StoreBuffer *owner, int phase); + void maybeCompact(StoreBuffer *owner); /* Add one item to the buffer. */ void put(StoreBuffer *owner, const T &t) { @@ -493,8 +489,6 @@ class StoreBuffer putFromAnyThread(bufferGeneric, CallbackRef(callback, key, data)); } - gcstats::Statistics& stats(); - /* Methods to mark the source of all edges in the store buffer. */ void markAll(JSTracer *trc); void markValues(JSTracer *trc) { bufferVal.mark(this, trc); } diff --git a/js/src/gc/Verifier.cpp b/js/src/gc/Verifier.cpp index 0428252db9b..f092b5e116a 100644 --- a/js/src/gc/Verifier.cpp +++ b/js/src/gc/Verifier.cpp @@ -503,10 +503,7 @@ js::gc::GCRuntime::endVerifyPostBarriers() if (!edges.init()) goto oom; trc->edges = &edges; - { - gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); - storeBuffer.markAll(trc); - } + storeBuffer.markAll(trc); /* Walk the heap to find any edges not the the |edges| set. */ trc->setTraceCallback(PostVerifierVisitEdge); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index 80cd4fb77f7..1ffacbdb245 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -6217,7 +6217,6 @@ void GCRuntime::minorGC(JS::gcreason::Reason reason) { #ifdef JSGC_GENERATIONAL - gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); minorGCRequested = false; TraceLogger *logger = TraceLoggerForMainThread(rt); AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); @@ -6232,7 +6231,6 @@ GCRuntime::minorGC(JSContext *cx, JS::gcreason::Reason reason) // Alternate to the runtime-taking form above which allows marking type // objects as needing pretenuring. #ifdef JSGC_GENERATIONAL - gcstats::AutoPhase ap(stats, gcstats::PHASE_MINOR_GC); minorGCRequested = false; TraceLogger *logger = TraceLoggerForMainThread(rt); AutoTraceLog logMinorGC(logger, TraceLogger::MinorGC); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index ffa6fc5bcf6..e063c9a984f 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -1660,43 +1660,6 @@ Quit(JSContext *cx, unsigned argc, jsval *vp) return false; } -static bool -StartTimingMutator(JSContext *cx, unsigned argc, jsval *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() > 0) { - JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, - JSSMSG_TOO_MANY_ARGS, "startTimingMutator"); - return false; - } - - cx->runtime()->gc.stats.startTimingMutator(); - args.rval().setUndefined(); - return true; -} - -static bool -StopTimingMutator(JSContext *cx, unsigned argc, jsval *vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - if (args.length() > 0) { - JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr, - JSSMSG_TOO_MANY_ARGS, "stopTimingMutator"); - return false; - } - - double mutator_ms, gc_ms; - cx->runtime()->gc.stats.stopTimingMutator(mutator_ms, gc_ms); - double total_ms = mutator_ms + gc_ms; - if (total_ms > 0) { - fprintf(gOutFile, "Mutator: %.3fms (%.1f%%), GC: %.3fms (%.1f%%)\n", - mutator_ms, mutator_ms / total_ms * 100.0, gc_ms, gc_ms / total_ms * 100.0); - } - - args.rval().setUndefined(); - return true; -} - static const char * ToSource(JSContext *cx, MutableHandleValue vp, JSAutoByteString *bytes) { @@ -4495,14 +4458,6 @@ static const JSFunctionSpecWithHelp shell_functions[] = { " Throw if the first two arguments are not the same (both +0 or both -0,\n" " both NaN, or non-zero and ===)."), - JS_FN_HELP("startTimingMutator", StartTimingMutator, 0, 0, -"startTimingMutator()", -" Start accounting time to mutator vs GC."), - - JS_FN_HELP("stopTimingMutator", StopTimingMutator, 0, 0, -"stopTimingMutator()", -" Stop accounting time to mutator vs GC and dump the results."), - JS_FN_HELP("throwError", ThrowError, 0, 0, "throwError()", " Throw an error from JS_ReportError."), From 986a44f67e746b0ee39b1c3e221737e5f1c69c19 Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Tue, 4 Nov 2014 16:52:38 -0800 Subject: [PATCH 027/153] Bug 1092833 - Deal with uninitialized slots in MacroAssembler::initGCSlots. (r=terrence) --- js/src/jit-test/tests/ion/bug1092833.js | 49 +++++++++++++++++ js/src/jit/IonMacroAssembler.cpp | 71 ++++++++++++++++++++----- js/src/jit/IonMacroAssembler.h | 3 ++ 3 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 js/src/jit-test/tests/ion/bug1092833.js diff --git a/js/src/jit-test/tests/ion/bug1092833.js b/js/src/jit-test/tests/ion/bug1092833.js new file mode 100644 index 00000000000..64a74fb28fd --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1092833.js @@ -0,0 +1,49 @@ +// Test that lexicals work with functions with many bindings. + +(function() { + var a01 + var b02 + var c03 + var d04 + var e05 + var f06 + var g07 + var h08 + let i09 + var j10 + var k11 + var l12 + var m13 + var n14 + var o15 + (function n14() { + assertEq(i09, undefined); + })() +})(); + +try { + (function() { + var a01 + var b02 + var c03 + var d04 + var e05 + var f06 + var g07 + var h08 + let i09 + var j10 + var k11 + var l12 + var m13 + var n14 + var o15 + (function n14() { + i12++ + })() + let i12 + })() +} catch (e) { + assertEq(e instanceof ReferenceError, true); + assertEq(e.message.indexOf("i12") > 0, true); +} diff --git a/js/src/jit/IonMacroAssembler.cpp b/js/src/jit/IonMacroAssembler.cpp index 397bd044f7e..11b8701b38f 100644 --- a/js/src/jit/IonMacroAssembler.cpp +++ b/js/src/jit/IonMacroAssembler.cpp @@ -27,6 +27,7 @@ #endif #include "jsinferinlines.h" #include "jsobjinlines.h" +#include "vm/Interpreter-inl.h" using namespace js; using namespace js::jit; @@ -956,12 +957,18 @@ MacroAssembler::copySlotsFromTemplate(Register obj, const NativeObject *template } void -MacroAssembler::fillSlotsWithUndefined(Address base, Register temp, uint32_t start, uint32_t end) +MacroAssembler::fillSlotsWithConstantValue(Address base, Register temp, + uint32_t start, uint32_t end, const Value &v) { + MOZ_ASSERT(v.isUndefined() || IsUninitializedLexical(v)); + + if (start >= end) + return; + #ifdef JS_NUNBOX32 // We only have a single spare register, so do the initialization as two // strided writes of the tag and body. - jsval_layout jv = JSVAL_TO_IMPL(UndefinedValue()); + jsval_layout jv = JSVAL_TO_IMPL(v); Address addr = base; move32(Imm32(jv.s.payload.i32), temp); @@ -973,22 +980,43 @@ MacroAssembler::fillSlotsWithUndefined(Address base, Register temp, uint32_t sta for (unsigned i = start; i < end; ++i, addr.offset += sizeof(HeapValue)) store32(temp, ToType(addr)); #else - moveValue(UndefinedValue(), temp); + moveValue(v, temp); for (uint32_t i = start; i < end; ++i, base.offset += sizeof(HeapValue)) storePtr(temp, base); #endif } -static uint32_t -FindStartOfUndefinedSlots(NativeObject *templateObj, uint32_t nslots) +void +MacroAssembler::fillSlotsWithUndefined(Address base, Register temp, uint32_t start, uint32_t end) +{ + fillSlotsWithConstantValue(base, temp, start, end, UndefinedValue()); +} + +void +MacroAssembler::fillSlotsWithUninitialized(Address base, Register temp, uint32_t start, uint32_t end) +{ + fillSlotsWithConstantValue(base, temp, start, end, MagicValue(JS_UNINITIALIZED_LEXICAL)); +} + +static void +FindStartOfUndefinedAndUninitializedSlots(NativeObject *templateObj, uint32_t nslots, + uint32_t *startOfUndefined, uint32_t *startOfUninitialized) { MOZ_ASSERT(nslots == templateObj->lastProperty()->slotSpan(templateObj->getClass())); MOZ_ASSERT(nslots > 0); - for (uint32_t first = nslots; first != 0; --first) { - if (templateObj->getSlot(first - 1) != UndefinedValue()) - return first; + uint32_t first = nslots; + for (; first != 0; --first) { + if (!IsUninitializedLexical(templateObj->getSlot(first - 1))) + break; } - return 0; + *startOfUninitialized = first; + for (; first != 0; --first) { + if (templateObj->getSlot(first - 1) != UndefinedValue()) { + *startOfUndefined = first; + return; + } + } + *startOfUndefined = 0; } void @@ -1011,16 +1039,28 @@ MacroAssembler::initGCSlots(Register obj, Register slots, NativeObject *template // logically into independent non-UndefinedValue writes to the head and // duplicated writes of UndefinedValue to the tail. For the majority of // objects, the "tail" will be the entire slot range. - uint32_t startOfUndefined = FindStartOfUndefinedSlots(templateObj, nslots); + // + // The template object may be a CallObject, in which case we need to + // account for uninitialized lexical slots as well as undefined + // slots. Unitialized lexical slots always appear at the very end of + // slots, after undefined. + uint32_t startOfUndefined = nslots; + uint32_t startOfUninitialized = nslots; + FindStartOfUndefinedAndUninitializedSlots(templateObj, nslots, + &startOfUndefined, &startOfUninitialized); MOZ_ASSERT(startOfUndefined <= nfixed); // Reserved slots must be fixed. + MOZ_ASSERT_IF(startOfUndefined != nfixed, startOfUndefined <= startOfUninitialized); + MOZ_ASSERT_IF(!templateObj->is(), startOfUninitialized == nslots); // Copy over any preserved reserved slots. copySlotsFromTemplate(obj, templateObj, 0, startOfUndefined); - // Fill the rest of the fixed slots with undefined. + // Fill the rest of the fixed slots with undefined and uninitialized. if (initFixedSlots) { fillSlotsWithUndefined(Address(obj, NativeObject::getFixedSlotOffset(startOfUndefined)), slots, - startOfUndefined, nfixed); + startOfUndefined, Min(startOfUninitialized, nfixed)); + size_t offset = NativeObject::getFixedSlotOffset(startOfUninitialized); + fillSlotsWithUninitialized(Address(obj, offset), slots, startOfUninitialized, nfixed); } if (ndynamic) { @@ -1028,7 +1068,14 @@ MacroAssembler::initGCSlots(Register obj, Register slots, NativeObject *template // register briefly for our slots base address. push(obj); loadPtr(Address(obj, NativeObject::offsetOfSlots()), obj); + + // Initially fill all dynamic slots with undefined. fillSlotsWithUndefined(Address(obj, 0), slots, 0, ndynamic); + + // Fill uninitialized slots if necessary. + fillSlotsWithUninitialized(Address(obj, 0), slots, startOfUninitialized - nfixed, + nslots - startOfUninitialized); + pop(obj); } } diff --git a/js/src/jit/IonMacroAssembler.h b/js/src/jit/IonMacroAssembler.h index a303a814fd9..7c8edc77a72 100644 --- a/js/src/jit/IonMacroAssembler.h +++ b/js/src/jit/IonMacroAssembler.h @@ -810,7 +810,10 @@ class MacroAssembler : public MacroAssemblerSpecific void allocateNonObject(Register result, Register temp, gc::AllocKind allocKind, Label *fail); void copySlotsFromTemplate(Register obj, const NativeObject *templateObj, uint32_t start, uint32_t end); + void fillSlotsWithConstantValue(Address addr, Register temp, uint32_t start, uint32_t end, + const Value &v); void fillSlotsWithUndefined(Address addr, Register temp, uint32_t start, uint32_t end); + void fillSlotsWithUninitialized(Address addr, Register temp, uint32_t start, uint32_t end); void initGCSlots(Register obj, Register temp, NativeObject *templateObj, bool initFixedSlots); public: From 0b64cfb0e6d5b9468a7ea907f43dd88315f9e17f Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Tue, 4 Nov 2014 08:57:25 -0500 Subject: [PATCH 028/153] Bug 1088043 - read and write nsTArrays more intelligently in IPC serialization; r=bent --- ipc/glue/IPCMessageUtils.h | 76 +++++++++++++++++++++++++++++++++++--- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/ipc/glue/IPCMessageUtils.h b/ipc/glue/IPCMessageUtils.h index bd3c69e45ba..60336217fa0 100644 --- a/ipc/glue/IPCMessageUtils.h +++ b/ipc/glue/IPCMessageUtils.h @@ -11,11 +11,13 @@ #include "chrome/common/ipc_message_utils.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/DebugOnly.h" #include "mozilla/TimeStamp.h" #ifdef XP_WIN #include "mozilla/TimeStamp_windows.h" #endif #include "mozilla/TypedEnum.h" +#include "mozilla/TypeTraits.h" #include "mozilla/IntegerTypeTraits.h" #include @@ -464,12 +466,51 @@ struct ParamTraits > { typedef FallibleTArray paramType; + // We write arrays of integer or floating-point data using a single pickling + // call, rather than writing each element individually. We deliberately do + // not use mozilla::IsPod here because it is perfectly reasonable to have + // a data structure T for which IsPod::value is true, yet also have a + // ParamTraits specialization. + static const bool sUseWriteBytes = (mozilla::IsIntegral::value || + mozilla::IsFloatingPoint::value); + + // Compute the byte length for |aNumElements| of type E. If that length + // would overflow an int, return false. Otherwise, return true and place + // the byte length in |aTotalLength|. + // + // Pickle's ReadBytes/WriteBytes interface takes lengths in ints, hence this + // dance. + static bool ByteLengthIsValid(size_t aNumElements, int* aTotalLength) { + static_assert(sizeof(int) == sizeof(int32_t), "int is an unexpected size!"); + + // nsTArray only handles sizes up to INT32_MAX. + if (aNumElements > size_t(INT32_MAX)) { + return false; + } + + int64_t numBytes = static_cast(aNumElements) * sizeof(E); + if (numBytes > int64_t(INT32_MAX)) { + return false; + } + + *aTotalLength = static_cast(numBytes); + return true; + } + static void Write(Message* aMsg, const paramType& aParam) { uint32_t length = aParam.Length(); WriteParam(aMsg, length); - for (uint32_t index = 0; index < length; index++) { - WriteParam(aMsg, aParam[index]); + + if (sUseWriteBytes) { + int pickledLength = 0; + mozilla::DebugOnly valid = ByteLengthIsValid(length, &pickledLength); + MOZ_ASSERT(valid); + aMsg->WriteBytes(aParam.Elements(), pickledLength); + } else { + for (uint32_t index = 0; index < length; index++) { + WriteParam(aMsg, aParam[index]); + } } } @@ -480,12 +521,35 @@ struct ParamTraits > return false; } - aResult->SetCapacity(length); - for (uint32_t index = 0; index < length; index++) { - E* element = aResult->AppendElement(); - if (!(element && ReadParam(aMsg, aIter, element))) { + if (sUseWriteBytes) { + int pickledLength = 0; + if (!ByteLengthIsValid(length, &pickledLength)) { return false; } + + const char* outdata; + if (!aMsg->ReadBytes(aIter, &outdata, pickledLength)) { + return false; + } + + E* elements = aResult->AppendElements(length); + if (!elements) { + return false; + } + + memcpy(elements, outdata, pickledLength); + } else { + if (!aResult->SetCapacity(length)) { + return false; + } + + for (uint32_t index = 0; index < length; index++) { + E* element = aResult->AppendElement(); + MOZ_ASSERT(element); + if (!ReadParam(aMsg, aIter, element)) { + return false; + } + } } return true; From 60b5c327818ca7be0ab7bb008697c429bdff6b7b Mon Sep 17 00:00:00 2001 From: Nathan Froyd Date: Tue, 4 Nov 2014 11:33:06 -0500 Subject: [PATCH 029/153] Bug 1093809 - make Pickle reading functions MOZ_WARN_UNUSED_RESULT; r=bent --- ipc/chromium/src/base/pickle.h | 46 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/ipc/chromium/src/base/pickle.h b/ipc/chromium/src/base/pickle.h index 3c9061dbba9..55c88a03152 100644 --- a/ipc/chromium/src/base/pickle.h +++ b/ipc/chromium/src/base/pickle.h @@ -11,6 +11,8 @@ #include "base/logging.h" #include "base/string16.h" +#include "mozilla/Attributes.h" + // This class provides facilities for basic binary value packing and unpacking. // // The Pickle class supports appending primitive values (ints, strings, etc.) @@ -67,31 +69,31 @@ class Pickle { // the Pickle, initialize *iter to NULL. If successful, these methods return // true. Otherwise, false is returned to indicate that the result could not // be extracted. - bool ReadBool(void** iter, bool* result) const; - bool ReadInt16(void** iter, int16_t* result) const; - bool ReadUInt16(void** iter, uint16_t* result) const; - bool ReadShort(void** iter, short* result) const; - bool ReadInt(void** iter, int* result) const; - bool ReadLong(void** iter, long* result) const; - bool ReadULong(void** iter, unsigned long* result) const; - bool ReadSize(void** iter, size_t* result) const; - bool ReadInt32(void** iter, int32_t* result) const; - bool ReadUInt32(void** iter, uint32_t* result) const; - bool ReadInt64(void** iter, int64_t* result) const; - bool ReadUInt64(void** iter, uint64_t* result) const; - bool ReadDouble(void** iter, double* result) const; - bool ReadIntPtr(void** iter, intptr_t* result) const; - bool ReadUnsignedChar(void** iter, unsigned char* result) const; - bool ReadString(void** iter, std::string* result) const; - bool ReadWString(void** iter, std::wstring* result) const; - bool ReadString16(void** iter, string16* result) const; - bool ReadData(void** iter, const char** data, int* length) const; - bool ReadBytes(void** iter, const char** data, int length, - uint32_t alignment = sizeof(memberAlignmentType)) const; + MOZ_WARN_UNUSED_RESULT bool ReadBool(void** iter, bool* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadInt16(void** iter, int16_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadUInt16(void** iter, uint16_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadShort(void** iter, short* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadInt(void** iter, int* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadLong(void** iter, long* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadULong(void** iter, unsigned long* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadSize(void** iter, size_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadInt32(void** iter, int32_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadUInt32(void** iter, uint32_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadInt64(void** iter, int64_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadUInt64(void** iter, uint64_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadDouble(void** iter, double* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadIntPtr(void** iter, intptr_t* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadUnsignedChar(void** iter, unsigned char* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadString(void** iter, std::string* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadWString(void** iter, std::wstring* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadString16(void** iter, string16* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadData(void** iter, const char** data, int* length) const; + MOZ_WARN_UNUSED_RESULT bool ReadBytes(void** iter, const char** data, int length, + uint32_t alignment = sizeof(memberAlignmentType)) const; // Safer version of ReadInt() checks for the result not being negative. // Use it for reading the object sizes. - bool ReadLength(void** iter, int* result) const; + MOZ_WARN_UNUSED_RESULT bool ReadLength(void** iter, int* result) const; // Methods for adding to the payload of the Pickle. These values are // appended to the end of the Pickle's payload. When reading values from a From 997000fd405a414d646c3b8e93467ee6aa15187c Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 21 Oct 2014 10:32:09 +1300 Subject: [PATCH 030/153] Bug 1083635. Part 1: Remove nsDisplayZoom::Paint since it's dead code. r=mattwoodrow --HG-- extra : rebase_source : bb053e5abb7f5a8ae17ab59af542db292ba35543 --- layout/base/nsDisplayList.cpp | 6 ------ layout/base/nsDisplayList.h | 1 - 2 files changed, 7 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index b7187729641..e27dab52f17 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -4418,12 +4418,6 @@ void nsDisplayZoom::HitTest(nsDisplayListBuilder *aBuilder, mList.HitTest(aBuilder, rect, aState, aOutFrames); } -void nsDisplayZoom::Paint(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx) -{ - mList.PaintForFrame(aBuilder, aCtx, mFrame, nsDisplayList::PAINT_DEFAULT); -} - bool nsDisplayZoom::ComputeVisibility(nsDisplayListBuilder *aBuilder, nsRegion *aVisibleRegion) { diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index de38c608e2f..4439d06fe9a 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -3180,7 +3180,6 @@ public: #endif virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) MOZ_OVERRIDE; - virtual void Paint(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx) MOZ_OVERRIDE; virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray *aOutFrames) MOZ_OVERRIDE; virtual bool ComputeVisibility(nsDisplayListBuilder* aBuilder, From 50bd448572f55dd7947a6ede19e21b04284a667e Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 21 Oct 2014 10:32:09 +1300 Subject: [PATCH 031/153] Bug 1083635. Part 2: Inline nsDisplayList::PaintForFrame into PaintRoot. r=mattwoodrow --HG-- extra : rebase_source : 1d313b18a2b0cc5894924299f421966a1f428799 --- layout/base/nsDisplayList.cpp | 31 +++++++++++++------------------ layout/base/nsDisplayList.h | 6 ------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index e27dab52f17..5335a760f0c 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1278,23 +1278,17 @@ nsDisplayList::ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder, return anyVisible; } -void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx, - uint32_t aFlags) { - PROFILER_LABEL("nsDisplayList", "PaintRoot", - js::ProfileEntry::Category::GRAPHICS); - PaintForFrame(aBuilder, aCtx, aBuilder->RootReferenceFrame(), aFlags); -} - /** * We paint by executing a layer manager transaction, constructing a * single layer representing the display list, and then making it the * root of the layer manager, drawing into the PaintedLayers. */ -void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx, - nsIFrame* aForFrame, - uint32_t aFlags) { +void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx, + uint32_t aFlags) { + PROFILER_LABEL("nsDisplayList", "PaintRoot", + js::ProfileEntry::Category::GRAPHICS); + nsRefPtr layerManager; bool widgetTransaction = false; bool allowRetaining = false; @@ -1347,7 +1341,8 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder, layerBuilder->DidBeginRetainedLayerTransaction(layerManager); } - nsPresContext* presContext = aForFrame->PresContext(); + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); nsIPresShell* presShell = presContext->GetPresShell(); NotifySubDocInvalidationFunc computeInvalidFunc = @@ -1364,7 +1359,7 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder, ContainerLayerParameters containerParameters (presShell->GetXResolution(), presShell->GetYResolution()); nsRefPtr root = layerBuilder-> - BuildContainerLayerFor(aBuilder, layerManager, aForFrame, nullptr, this, + BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this, containerParameters, nullptr); nsIDocument* document = nullptr; @@ -1384,11 +1379,11 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder, nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); - nsRect viewport(aBuilder->ToReferenceFrame(aForFrame), aForFrame->GetSize()); + nsRect viewport(aBuilder->ToReferenceFrame(frame), frame->GetSize()); root->SetFrameMetrics( - nsDisplayScrollLayer::ComputeFrameMetrics(aForFrame, rootScrollFrame, - aBuilder->FindReferenceFrameFor(aForFrame), + nsDisplayScrollLayer::ComputeFrameMetrics(frame, rootScrollFrame, + aBuilder->FindReferenceFrameFor(frame), root, FrameMetrics::NULL_SCROLL_ID, viewport, !isRoot, isRoot, containerParameters)); @@ -1418,7 +1413,7 @@ void nsDisplayList::PaintForFrame(nsDisplayListBuilder* aBuilder, // but they still need the invalidation state bits cleared in order for // invalidation for CSS/SMIL animation to work properly. (document && document->IsBeingUsedAsImage())) { - aForFrame->ClearInvalidationStateBits(); + frame->ClearInvalidationStateBits(); } bool temp = aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap()); diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index 4439d06fe9a..e6496da4d64 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -1672,12 +1672,6 @@ public: }; void PaintRoot(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx, uint32_t aFlags); - /** - * Like PaintRoot, but used for internal display sublists. - * aForFrame is the frame that the list is associated with. - */ - void PaintForFrame(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx, - nsIFrame* aForFrame, uint32_t aFlags); /** * Get the bounds. Takes the union of the bounds of all children. * The result is not cached. From 7d45b99ff888d6794709bf72f557acc250f745be Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 21 Oct 2014 10:32:10 +1300 Subject: [PATCH 032/153] Bug 1083635. Part 3: Flush displaylist dump before calling PaintRoot. r=mattwoodrow --HG-- extra : rebase_source : 6da7f168218036a13be7dcd73ed8b3c552dcace1 --- layout/base/nsLayoutUtils.cpp | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index bc44caebe81..9a83a19ee5b 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3066,7 +3066,7 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram #ifdef MOZ_DUMP_PAINTING FILE* savedDumpFile = gfxUtils::sDumpPaintFile; - std::stringstream ss; + UniquePtr ss = MakeUnique(); if (gfxUtils::DumpPaintList() || gfxUtils::sDumpPainting) { if (gfxUtils::sDumpPaintingToFile) { nsCString string("dump-"); @@ -3077,13 +3077,18 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram gfxUtils::sDumpPaintFile = stderr; } if (gfxUtils::sDumpPaintingToFile) { - ss << ""; + *ss << ""; } - ss << nsPrintfCString("Painting --- before optimization (dirty %d,%d,%d,%d):\n", + *ss << nsPrintfCString("Painting --- before optimization (dirty %d,%d,%d,%d):\n", dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height).get(); - nsFrame::PrintDisplayList(&builder, list, ss, gfxUtils::sDumpPaintingToFile); + nsFrame::PrintDisplayList(&builder, list, *ss, gfxUtils::sDumpPaintingToFile); if (gfxUtils::sDumpPaintingToFile) { - ss << ""; + *ss << ""; } - ss << "Painting --- after optimization:\n"; - nsFrame::PrintDisplayList(&builder, list, ss, gfxUtils::sDumpPaintingToFile); + *ss << "Painting --- after optimization:\n"; + nsFrame::PrintDisplayList(&builder, list, *ss, gfxUtils::sDumpPaintingToFile); - ss << "Painting --- retained layer tree:\n"; + *ss << "Painting --- retained layer tree:\n"; nsIWidget* widget = aFrame->GetNearestWidget(); if (widget) { nsRefPtr layerManager = widget->GetLayerManager(); if (layerManager) { - FrameLayerBuilder::DumpRetainedLayerTree(layerManager, ss, + FrameLayerBuilder::DumpRetainedLayerTree(layerManager, *ss, gfxUtils::sDumpPaintingToFile); } } if (gfxUtils::sDumpPaintingToFile) { - ss << ""; + *ss << ""; } - fprint_stderr(gfxUtils::sDumpPaintFile, ss); + fprint_stderr(gfxUtils::sDumpPaintFile, *ss); if (gfxUtils::sDumpPaintingToFile) { fclose(gfxUtils::sDumpPaintFile); From c72141ba760d739a4a3e94fc681bb6ce4fb5d80e Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 21 Oct 2014 10:32:10 +1300 Subject: [PATCH 033/153] Bug 1083635. Part 4: Return LayerManager from nsDisplayList::PaintRoot, and dump its contents in nsLayoutUtils::PaintFrame. r=mattwoodrow --HG-- extra : rebase_source : 8c39aebedb275b56eda4a740b63ae4e00523c206 --- layout/base/nsDisplayList.cpp | 11 ++++++----- layout/base/nsDisplayList.h | 10 ++++++---- layout/base/nsLayoutUtils.cpp | 21 ++++++--------------- layout/base/nsPresShell.cpp | 4 +++- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/layout/base/nsDisplayList.cpp b/layout/base/nsDisplayList.cpp index 5335a760f0c..7d95861d45f 100644 --- a/layout/base/nsDisplayList.cpp +++ b/layout/base/nsDisplayList.cpp @@ -1283,9 +1283,9 @@ nsDisplayList::ComputeVisibilityForSublist(nsDisplayListBuilder* aBuilder, * single layer representing the display list, and then making it the * root of the layer manager, drawing into the PaintedLayers. */ -void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, - nsRenderingContext* aCtx, - uint32_t aFlags) { +already_AddRefed nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx, + uint32_t aFlags) { PROFILER_LABEL("nsDisplayList", "PaintRoot", js::ProfileEntry::Category::GRAPHICS); @@ -1311,7 +1311,7 @@ void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, if (!layerManager) { if (!aCtx) { NS_WARNING("Nowhere to paint into"); - return; + return nullptr; } layerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN); } @@ -1369,7 +1369,7 @@ void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, if (!root) { layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder); - return; + return nullptr; } // Root is being scaled up by the X/Y resolution. Scale it back down. root->SetPostScale(1.0f/containerParameters.mXScale, @@ -1468,6 +1468,7 @@ void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, } layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder); + return layerManager.forget(); } uint32_t nsDisplayList::Count() const { diff --git a/layout/base/nsDisplayList.h b/layout/base/nsDisplayList.h index e6496da4d64..10f35a8228d 100644 --- a/layout/base/nsDisplayList.h +++ b/layout/base/nsDisplayList.h @@ -1657,10 +1657,11 @@ public: * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to compressed mode * to avoid short cut optimizations. * - * ComputeVisibility must be called before Paint. - * * This must only be called on the root display list of the display list * tree. + * + * We return the layer manager used for painting --- mainly so that + * callers can dump its layer tree if necessary. */ enum { PAINT_DEFAULT = 0, @@ -1670,8 +1671,9 @@ public: PAINT_NO_COMPOSITE = 0x08, PAINT_COMPRESSED = 0x10 }; - void PaintRoot(nsDisplayListBuilder* aBuilder, nsRenderingContext* aCtx, - uint32_t aFlags); + already_AddRefed PaintRoot(nsDisplayListBuilder* aBuilder, + nsRenderingContext* aCtx, + uint32_t aFlags); /** * Get the bounds. Takes the union of the bounds of all children. * The result is not cached. diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp index 9a83a19ee5b..04975014559 100644 --- a/layout/base/nsLayoutUtils.cpp +++ b/layout/base/nsLayoutUtils.cpp @@ -3124,7 +3124,8 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram flags |= nsDisplayList::PAINT_COMPRESSED; } - list.PaintRoot(&builder, aRenderingContext, flags); + nsRefPtr layerManager = + list.PaintRoot(&builder, aRenderingContext, flags); #ifdef MOZ_DUMP_PAINTING if (gfxUtils::DumpPaintList() || gfxUtils::sDumpPainting) { @@ -3134,14 +3135,10 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram *ss << "Painting --- after optimization:\n"; nsFrame::PrintDisplayList(&builder, list, *ss, gfxUtils::sDumpPaintingToFile); - *ss << "Painting --- retained layer tree:\n"; - nsIWidget* widget = aFrame->GetNearestWidget(); - if (widget) { - nsRefPtr layerManager = widget->GetLayerManager(); - if (layerManager) { - FrameLayerBuilder::DumpRetainedLayerTree(layerManager, *ss, - gfxUtils::sDumpPaintingToFile); - } + *ss << "Painting --- layer tree:\n"; + if (layerManager) { + FrameLayerBuilder::DumpRetainedLayerTree(layerManager, *ss, + gfxUtils::sDumpPaintingToFile); } if (gfxUtils::sDumpPaintingToFile) { *ss << ""; @@ -3177,12 +3174,6 @@ nsLayoutUtils::PaintFrame(nsRenderingContext* aRenderingContext, nsIFrame* aFram } if (builder.WillComputePluginGeometry()) { - nsRefPtr layerManager; - nsIWidget* widget = aFrame->GetNearestWidget(); - if (widget) { - layerManager = widget->GetLayerManager(); - } - rootPresContext->ComputePluginGeometryUpdates(aFrame, &builder, &list); // We're not going to get a WillPaintWindow event here if we didn't do diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 1154e465533..377320e7712 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -5255,7 +5255,9 @@ PresShell::PaintRangePaintInfo(nsTArray >* aItems, ctx->SetMatrix(initialTM.Translate(rootOffset)); aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y); nsRegion visible(aArea); - rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &rc, nsDisplayList::PAINT_DEFAULT); + nsRefPtr layerManager = + rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &rc, + nsDisplayList::PAINT_DEFAULT); aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y); } From d0f1ae646ea1e490e3693e6ae05f7a2cd2a10d3c Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 4 Nov 2014 17:05:44 +1300 Subject: [PATCH 034/153] Bug 1083635. Part 4.2: Create nsDOMWindowUtils::UpdateLayerTree and use it to flush layers in reftest content processes instead of doing a DRAW_WIDGET_LAYERS drawWindow. r=mattwoodrow --HG-- extra : rebase_source : b1db1f0171180ed0a7ca36f92653cb702124fc95 --- dom/base/nsDOMWindowUtils.cpp | 10 ++++++++++ dom/interfaces/base/nsIDOMWindowUtils.idl | 7 ++++++- layout/base/nsIPresShell.h | 4 ++-- layout/tools/reftest/reftest-content.js | 8 +------- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 165f3794efa..71172e71993 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -271,6 +271,16 @@ nsDOMWindowUtils::Redraw(uint32_t aCount, uint32_t *aDurationOut) return NS_ERROR_FAILURE; } +NS_IMETHODIMP +nsDOMWindowUtils::UpdateLayerTree() +{ + if (nsIPresShell* presShell = GetPresShell()) { + nsRefPtr vm = presShell->GetViewManager(); + vm->ProcessPendingUpdates(); + } + return NS_OK; +} + NS_IMETHODIMP nsDOMWindowUtils::SetCSSViewport(float aWidthPx, float aHeightPx) { diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index ecbb125b787..63d3165acbc 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -51,7 +51,7 @@ interface nsITranslationNodeList; interface nsIJSRAIIHelper; interface nsIContentPermissionRequest; -[scriptable, uuid(f7e4d5da-4dd0-455a-b448-d0224c17fd10)] +[scriptable, uuid(01e0c3ca-37e7-4ab4-bd22-3e99d8d23336)] interface nsIDOMWindowUtils : nsISupports { /** @@ -103,6 +103,11 @@ interface nsIDOMWindowUtils : nsISupports { */ unsigned long redraw([optional] in unsigned long aCount); + /** + * Force a synchronous layer transaction for this window if necessary. + */ + void updateLayerTree(); + /** * Set the CSS viewport to be |widthPx| x |heightPx| in units of CSS * pixels, regardless of the size of the enclosing widget/view. diff --git a/layout/base/nsIPresShell.h b/layout/base/nsIPresShell.h index aa573cf2320..eb48f3b9afa 100644 --- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -1102,8 +1102,8 @@ public: RENDER_DRAWWINDOW_NOT_FLUSHING = 0x40 }; virtual nsresult RenderDocument(const nsRect& aRect, uint32_t aFlags, - nscolor aBackgroundColor, - gfxContext* aRenderedContext) = 0; + nscolor aBackgroundColor, + gfxContext* aRenderedContext) = 0; /** * Renders a node aNode to a surface and returns it. The aRegion may be used diff --git a/layout/tools/reftest/reftest-content.js b/layout/tools/reftest/reftest-content.js index 2c307e1bafc..5895fe89066 100644 --- a/layout/tools/reftest/reftest-content.js +++ b/layout/tools/reftest/reftest-content.js @@ -784,13 +784,7 @@ function SynchronizeForSnapshot(flags) } } - var dummyCanvas = content.document.createElementNS(XHTML_NS, "canvas"); - dummyCanvas.setAttribute("width", 1); - dummyCanvas.setAttribute("height", 1); - - var ctx = dummyCanvas.getContext("2d"); - var flags = ctx.DRAWWINDOW_DRAW_CARET | ctx.DRAWWINDOW_DRAW_VIEW | ctx.DRAWWINDOW_USE_WIDGET_LAYERS; - ctx.drawWindow(content, 0, 0, 1, 1, "rgb(255,255,255)", flags); + windowUtils().updateLayerTree(); // Setup async scroll offsets now, because any scrollable layers should // have had their AsyncPanZoomControllers created. From c2d5639aae76844ddf15f04ecc01d7030820d329 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Fri, 24 Oct 2014 12:20:01 +1300 Subject: [PATCH 035/153] Bug 1083635. Part 4.5: Avoid requesting PAINT_WIDGET_LAYERS if we're in a content process. r=mattwoodrow --HG-- extra : rebase_source : 2559a0f44319264fe6395ef833269fb412f18502 --- layout/base/nsPresShell.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index 377320e7712..0f592b7752a 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -4915,7 +4915,14 @@ PresShell::RenderDocument(const nsRect& aRect, uint32_t aFlags, nsView* view = rootFrame->GetView(); if (view && view->GetWidget() && nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) { - flags |= nsLayoutUtils::PAINT_WIDGET_LAYERS; + LayerManager* layerManager = view->GetWidget()->GetLayerManager(); + // ClientLayerManagers in content processes don't support + // taking snapshots. + if (layerManager && + (!layerManager->AsClientLayerManager() || + XRE_GetProcessType() == GeckoProcessType_Default)) { + flags |= nsLayoutUtils::PAINT_WIDGET_LAYERS; + } } } if (!(aFlags & RENDER_CARET)) { From bd30ce7c71ef08728ac850dc69cc080b92c946fa Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 21 Oct 2014 10:32:10 +1300 Subject: [PATCH 036/153] Bug 1083635. Part 5: ClientLayerManager::BeginTransactionWithTarget should assert if we're not going to take a snapshot. r=mattwoodrow --HG-- extra : rebase_source : 07cea353735743670cc82031bcbe738932f56829 --- gfx/layers/client/ClientLayerManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gfx/layers/client/ClientLayerManager.cpp b/gfx/layers/client/ClientLayerManager.cpp index d2c8e5dacf8..f38e5ef6b47 100644 --- a/gfx/layers/client/ClientLayerManager.cpp +++ b/gfx/layers/client/ClientLayerManager.cpp @@ -217,6 +217,9 @@ ClientLayerManager::BeginTransactionWithTarget(gfxContext* aTarget) // to it. This will happen at the end of the transaction. if (aTarget && XRE_GetProcessType() == GeckoProcessType_Default) { mShadowTarget = aTarget; + } else { + NS_ASSERTION(!aTarget, + "Content-process ClientLayerManager::BeginTransactionWithTarget not supported"); } // If this is a new paint, increment the paint sequence number. From e2ca2f25988cab3ed0ea29f4180badbcab031614 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 21 Oct 2014 10:32:11 +1300 Subject: [PATCH 037/153] Bug 1083635. Part 6: Rename viewport to scrollport in ScrollFrameHelper::ComputeFrameMetrics. r=mattwoodrow --HG-- extra : rebase_source : 188a2ddd979eacdcfee18b700d6c4f0c2ced48a0 --- layout/generic/nsGfxScrollFrame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index f927b5f352d..6b51be74c97 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -3047,10 +3047,10 @@ ScrollFrameHelper::ComputeFrameMetrics(Layer* aLayer, nsRect* aClipRect, nsTArray* aOutput) const { - nsRect viewport = mScrollPort + + nsRect scrollport = mScrollPort + mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame); if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) { - *aClipRect = viewport; + *aClipRect = scrollport; } if (!mShouldBuildScrollableLayer || BuildScrollContainerLayers()) { @@ -3062,7 +3062,7 @@ ScrollFrameHelper::ComputeFrameMetrics(Layer* aLayer, *aOutput->AppendElement() = nsDisplayScrollLayer::ComputeFrameMetrics(mScrolledFrame, mOuter, aContainerReferenceFrame, aLayer, mScrollParentID, - viewport, false, false, aParameters); + scrollport, false, false, aParameters); } bool From cdb7a37f1ab13e68aa5315a749423583e950e73f Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Tue, 28 Oct 2014 17:34:58 +1300 Subject: [PATCH 038/153] Bug 1080205. Part 1: Don't add clip rects to async-scrolled layers for root scrollframes, since the FrameMetrics to scroll those layers will be set on a ContainerLayer parent. r=tn --HG-- extra : rebase_source : b8fc0e3c97d5be50443734ed80caafabd37cf1a3 --- layout/generic/nsGfxScrollFrame.cpp | 8 +++++++- layout/generic/nsGfxScrollFrame.h | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/layout/generic/nsGfxScrollFrame.cpp b/layout/generic/nsGfxScrollFrame.cpp index 6b51be74c97..6ef75c4e747 100644 --- a/layout/generic/nsGfxScrollFrame.cpp +++ b/layout/generic/nsGfxScrollFrame.cpp @@ -2800,6 +2800,7 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, mOuter->PresContext()->IsRootContentDocument(); if (aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping()) { + mAddClipRectToLayer = false; // If we are a root scroll frame that has a display port we want to add // scrollbars, they will be children of the scrollable layer, but they get @@ -2829,6 +2830,9 @@ ScrollFrameHelper::BuildDisplayList(nsDisplayListBuilder* aBuilder, return; } + mAddClipRectToLayer = + !(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden()); + // Overflow clipping can never clip frames outside our subtree, so there // is no need to worry about whether we are a moving frame that might clip // non-moving frames. @@ -3049,7 +3053,9 @@ ScrollFrameHelper::ComputeFrameMetrics(Layer* aLayer, { nsRect scrollport = mScrollPort + mOuter->GetOffsetToCrossDoc(aContainerReferenceFrame); - if (!(mIsRoot && mOuter->PresContext()->PresShell()->GetIsViewportOverridden())) { + // Root scrollframes have FrameMetrics and clipping on their container layers, + // so don't also apply clipping here. + if (mAddClipRectToLayer) { *aClipRect = scrollport; } diff --git a/layout/generic/nsGfxScrollFrame.h b/layout/generic/nsGfxScrollFrame.h index 447828b94e8..8c27379c761 100644 --- a/layout/generic/nsGfxScrollFrame.h +++ b/layout/generic/nsGfxScrollFrame.h @@ -450,6 +450,8 @@ public: // If true, the layer should always be active because we always build a // scrollable layer. Used for asynchronous scrolling. bool mShouldBuildScrollableLayer:1; + // If true, add clipping in ScrollFrameHelper::ComputeFrameMetrics. + bool mAddClipRectToLayer:1; // True if this frame has been scrolled at least once bool mHasBeenScrolled:1; From 628c45fc975b51618215cbc54f2128024b4425c0 Mon Sep 17 00:00:00 2001 From: Robert O'Callahan Date: Thu, 30 Oct 2014 16:33:55 +1300 Subject: [PATCH 039/153] Bug 1080205. Part 2: Add test. r=tn --HG-- extra : rebase_source : 9f8a41ae255e132e572fdb5012f851554fde69d9 --- gfx/layers/ipc/PLayerTransaction.ipdl | 2 +- .../async-scrolling/iframe-1-ref.html | 10 +++++ layout/reftests/async-scrolling/iframe-1.html | 12 +++++ layout/reftests/async-scrolling/reftest.list | 1 + layout/tools/reftest/reftest-content.js | 44 ++++++++++++------- 5 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 layout/reftests/async-scrolling/iframe-1-ref.html create mode 100644 layout/reftests/async-scrolling/iframe-1.html diff --git a/gfx/layers/ipc/PLayerTransaction.ipdl b/gfx/layers/ipc/PLayerTransaction.ipdl index ded6f6ce48d..1360d48e987 100644 --- a/gfx/layers/ipc/PLayerTransaction.ipdl +++ b/gfx/layers/ipc/PLayerTransaction.ipdl @@ -84,7 +84,7 @@ parent: // The next time the layer tree is composited, add this async scroll offset in // CSS pixels for the given ViewID. // Useful for testing rendering of async scrolling. - async SetAsyncScrollOffset(ViewID id, int32_t x, int32_t y); + sync SetAsyncScrollOffset(ViewID id, int32_t x, int32_t y); // Drop any front buffers that might be retained on the compositor // side. diff --git a/layout/reftests/async-scrolling/iframe-1-ref.html b/layout/reftests/async-scrolling/iframe-1-ref.html new file mode 100644 index 00000000000..76537c52c10 --- /dev/null +++ b/layout/reftests/async-scrolling/iframe-1-ref.html @@ -0,0 +1,10 @@ + + + + diff --git a/layout/reftests/async-scrolling/iframe-1.html b/layout/reftests/async-scrolling/iframe-1.html new file mode 100644 index 00000000000..fbd89d661ba --- /dev/null +++ b/layout/reftests/async-scrolling/iframe-1.html @@ -0,0 +1,12 @@ + + + + diff --git a/layout/reftests/async-scrolling/reftest.list b/layout/reftests/async-scrolling/reftest.list index f85725e7c7c..cdde191e288 100644 --- a/layout/reftests/async-scrolling/reftest.list +++ b/layout/reftests/async-scrolling/reftest.list @@ -3,6 +3,7 @@ pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enable pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == bg-fixed-cover-2.html bg-fixed-cover-2-ref.html pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == bg-fixed-cover-3.html bg-fixed-cover-3-ref.html pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == element-1.html element-1-ref.html +pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) pref(layers.force-active,true) skip-if(!asyncPanZoom) == iframe-1.html iframe-1-ref.html pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == nested-1.html nested-1-ref.html pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-1.html position-fixed-1-ref.html pref(layout.async-containerless-scrolling.enabled,true) pref(apz.subframe.enabled,true) skip-if(!asyncPanZoom) == position-fixed-2.html position-fixed-2-ref.html diff --git a/layout/tools/reftest/reftest-content.js b/layout/tools/reftest/reftest-content.js index 5895fe89066..c49d2e24235 100644 --- a/layout/tools/reftest/reftest-content.js +++ b/layout/tools/reftest/reftest-content.js @@ -57,9 +57,13 @@ function webNavigation() { return docShell.QueryInterface(CI.nsIWebNavigation); } +function windowUtilsForWindow(w) { + return w.QueryInterface(CI.nsIInterfaceRequestor) + .getInterface(CI.nsIDOMWindowUtils); +} + function windowUtils() { - return content.QueryInterface(CI.nsIInterfaceRequestor) - .getInterface(CI.nsIDOMWindowUtils); + return windowUtilsForWindow(content); } function IDForEventTarget(event) @@ -208,28 +212,33 @@ function setupDisplayport(contentRootElement) { return; } - function setupDisplayportForElement(element) { + function setupDisplayportForElement(element, winUtils) { var dpw = attrOrDefault(element, "reftest-displayport-w", 0); var dph = attrOrDefault(element, "reftest-displayport-h", 0); var dpx = attrOrDefault(element, "reftest-displayport-x", 0); var dpy = attrOrDefault(element, "reftest-displayport-y", 0); if (dpw !== 0 || dph !== 0 || dpx != 0 || dpy != 0) { LogInfo("Setting displayport to "); - windowUtils().setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1); + winUtils.setDisplayPortForElement(dpx, dpy, dpw, dph, element, 1); } } - function setupDisplayportForElementSubtree(element) { - setupDisplayportForElement(element); + function setupDisplayportForElementSubtree(element, winUtils) { + setupDisplayportForElement(element, winUtils); for (var c = element.firstElementChild; c; c = c.nextElementSibling) { - setupDisplayportForElementSubtree(c); + setupDisplayportForElementSubtree(c, winUtils); + } + if (element.contentDocument) { + LogInfo("Descending into subdocument"); + setupDisplayportForElementSubtree(element.contentDocument.documentElement, + windowUtilsForWindow(element.contentWindow)); } } if (contentRootElement.hasAttribute("reftest-async-scroll")) { - setupDisplayportForElementSubtree(contentRootElement); + setupDisplayportForElementSubtree(contentRootElement, windowUtils()); } else { - setupDisplayportForElement(contentRootElement); + setupDisplayportForElement(contentRootElement, windowUtils()); } } @@ -241,14 +250,14 @@ function setupAsyncScrollOffsets(options) { return; } - function setupAsyncScrollOffsetsForElement(element) { + function setupAsyncScrollOffsetsForElement(element, winUtils) { var sx = attrOrDefault(element, "reftest-async-scroll-x", 0); var sy = attrOrDefault(element, "reftest-async-scroll-y", 0); if (sx != 0 || sy != 0) { try { // This might fail when called from RecordResult since layers // may not have been constructed yet - windowUtils().setAsyncScrollOffset(element, sx, sy); + winUtils.setAsyncScrollOffset(element, sx, sy); } catch (e) { if (!options.allowFailure) { throw e; @@ -257,16 +266,21 @@ function setupAsyncScrollOffsets(options) { } } - function setupAsyncScrollOffsetsForElementSubtree(element) { - setupAsyncScrollOffsetsForElement(element); + function setupAsyncScrollOffsetsForElementSubtree(element, winUtils) { + setupAsyncScrollOffsetsForElement(element, winUtils); for (var c = element.firstElementChild; c; c = c.nextElementSibling) { - setupAsyncScrollOffsetsForElementSubtree(c); + setupAsyncScrollOffsetsForElementSubtree(c, winUtils); + } + if (element.contentDocument) { + LogInfo("Descending into subdocument (async offsets)"); + setupAsyncScrollOffsetsForElementSubtree(element.contentDocument.documentElement, + windowUtilsForWindow(element.contentWindow)); } } var asyncScroll = contentRootElement.hasAttribute("reftest-async-scroll"); if (asyncScroll) { - setupAsyncScrollOffsetsForElementSubtree(contentRootElement); + setupAsyncScrollOffsetsForElementSubtree(contentRootElement, windowUtils()); } } From d146c5b08bf5ee11b988b676cbbd283e14911ea7 Mon Sep 17 00:00:00 2001 From: "Pankaj Malhotra(:bitgeeky)" Date: Tue, 4 Nov 2014 16:59:40 -0800 Subject: [PATCH 040/153] Bug 1091270 - Move isURL out of automationutils; r=jgriffin --- build/automationutils.py | 7 ------- layout/tools/reftest/runreftest.py | 4 ++-- testing/mochitest/mochitest_options.py | 5 +++-- testing/mochitest/runtests.py | 4 ++-- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/build/automationutils.py b/build/automationutils.py index 73fcf2782fb..5973342bed2 100644 --- a/build/automationutils.py +++ b/build/automationutils.py @@ -13,7 +13,6 @@ import signal import subprocess import sys import tempfile -from urlparse import urlparse import zipfile import mozinfo @@ -21,7 +20,6 @@ __all__ = [ "ZipFileReader", "addCommonOptions", "dumpLeakLog", - "isURL", "processLeakLog", 'KeyValueParseError', 'parseKeyValue', @@ -107,11 +105,6 @@ class ZipFileReader(object): for name in self._zipfile.namelist(): self._extractname(name, path) -def isURL(thing): - """Return True if |thing| looks like a URL.""" - # We want to download URLs like http://... but not Windows paths like c:\... - return len(urlparse(thing).scheme) >= 2 - # Python does not provide strsignal() even in the very latest 3.x. # This is a reasonable fake. def strsig(n): diff --git a/layout/tools/reftest/runreftest.py b/layout/tools/reftest/runreftest.py index 272edbf58c7..ac2bf771e72 100644 --- a/layout/tools/reftest/runreftest.py +++ b/layout/tools/reftest/runreftest.py @@ -7,6 +7,7 @@ Runs the reftest test harness. """ from optparse import OptionParser +from urlparse import urlparse import collections import multiprocessing import os @@ -24,7 +25,6 @@ from automationutils import ( addCommonOptions, dumpScreen, environment, - isURL, processLeakLog ) import mozcrash @@ -786,7 +786,7 @@ Are you executing $objdir/_tests/reftest/runreftest.py?""" \ if options.xrePath is None: options.xrePath = os.path.dirname(options.app) - if options.symbolsPath and not isURL(options.symbolsPath): + if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2: options.symbolsPath = reftest.getFullPath(options.symbolsPath) options.utilityPath = reftest.getFullPath(options.utilityPath) diff --git a/testing/mochitest/mochitest_options.py b/testing/mochitest/mochitest_options.py index 2e4a03c0bf4..0e92cabad46 100644 --- a/testing/mochitest/mochitest_options.py +++ b/testing/mochitest/mochitest_options.py @@ -2,13 +2,14 @@ # 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/. +from urlparse import urlparse import mozinfo import moznetwork import optparse import os import tempfile -from automationutils import addCommonOptions, isURL +from automationutils import addCommonOptions from mozprofile import DEFAULT_PORTS here = os.path.abspath(os.path.dirname(__file__)) @@ -504,7 +505,7 @@ class MochitestOptions(optparse.OptionParser): if options.certPath: options.certPath = mochitest.getFullPath(options.certPath) - if options.symbolsPath and not isURL(options.symbolsPath): + if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2: options.symbolsPath = mochitest.getFullPath(options.symbolsPath) # Set server information on the options object diff --git a/testing/mochitest/runtests.py b/testing/mochitest/runtests.py index 54e6c047a4a..e6f89d9e699 100644 --- a/testing/mochitest/runtests.py +++ b/testing/mochitest/runtests.py @@ -12,6 +12,7 @@ import sys SCRIPT_DIR = os.path.abspath(os.path.realpath(os.path.dirname(__file__))) sys.path.insert(0, SCRIPT_DIR); +from urlparse import urlparse import ctypes import glob import json @@ -35,7 +36,6 @@ import bisection from automationutils import ( environment, - isURL, KeyValueParseError, parseKeyValue, processLeakLog, @@ -2125,7 +2125,7 @@ def main(): options.utilityPath = mochitest.getFullPath(options.utilityPath) options.certPath = mochitest.getFullPath(options.certPath) - if options.symbolsPath and not isURL(options.symbolsPath): + if options.symbolsPath and len(urlparse(options.symbolsPath).scheme) < 2: options.symbolsPath = mochitest.getFullPath(options.symbolsPath) return_code = mochitest.runTests(options) From 7e118c77aa4377426502cbcecb8c30f2cc8e7cfd Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 4 Nov 2014 18:21:47 -0700 Subject: [PATCH 041/153] Bug 1091329 - Optimize writes to reference members of TypedObjects, r=nmatsakis,jandem. --- .../tests/TypedObject/jit-write-references.js | 83 +++++++++++++ js/src/jit/CodeGenerator.cpp | 94 ++++++++++++++- js/src/jit/CodeGenerator.h | 2 + js/src/jit/Ion.cpp | 5 + js/src/jit/IonBuilder.cpp | 102 ++++++++++++++-- js/src/jit/IonBuilder.h | 15 +++ js/src/jit/IonTypes.h | 1 + js/src/jit/JitCompartment.h | 2 + js/src/jit/LIR-Common.h | 43 +++++++ js/src/jit/LIR.h | 1 + js/src/jit/LOpcodes.h | 2 + js/src/jit/Lowering.cpp | 43 +++++++ js/src/jit/Lowering.h | 3 + js/src/jit/MIR.h | 112 +++++++++++++++++- js/src/jit/MOpcodes.h | 3 + js/src/jit/ParallelSafetyAnalysis.cpp | 3 + js/src/jit/TypePolicy.cpp | 28 +++++ js/src/jit/TypePolicy.h | 13 ++ js/src/jit/VMFunctions.cpp | 14 +++ js/src/jit/VMFunctions.h | 4 + js/src/jit/arm/MacroAssembler-arm.cpp | 18 ++- js/src/jit/arm/MacroAssembler-arm.h | 6 +- js/src/jit/mips/MacroAssembler-mips.cpp | 18 ++- js/src/jit/mips/MacroAssembler-mips.h | 6 +- js/src/jit/x64/MacroAssembler-x64.h | 9 +- js/src/jit/x86/MacroAssembler-x86.h | 9 +- 26 files changed, 603 insertions(+), 36 deletions(-) create mode 100644 js/src/jit-test/tests/TypedObject/jit-write-references.js diff --git a/js/src/jit-test/tests/TypedObject/jit-write-references.js b/js/src/jit-test/tests/TypedObject/jit-write-references.js new file mode 100644 index 00000000000..2ea25004ca7 --- /dev/null +++ b/js/src/jit-test/tests/TypedObject/jit-write-references.js @@ -0,0 +1,83 @@ +if (typeof TypedObject === "undefined") + quit(); + +var T = TypedObject; + +var ObjectStruct = new T.StructType({f: T.Object}); +var StringStruct = new T.StructType({f: T.string}); +var ValueStruct = new T.StructType({f: T.Any}); + +// Suppress ion compilation of the global script. +with({}){} + +var o = new ObjectStruct(); +var s = new StringStruct(); +var v = new ValueStruct(); + +// Make sure that unboxed null pointers on the stack are marked correctly. +whatever = new Array(); +function testGC(o, p) { + for (var i = 0; i < 5; i++) { + minorgc(); + o.f = p; + whatever.push(new Array()); // minorgc needs garbage before it scans the stack. + } +} +testGC(o, {}); +testGC(o, null); + +// Test writing various things to an object field. +function writeObject(o, v, expected) { + o.f = v; + assertEq(typeof o.f, "object"); + assertEq("" + o.f, expected); +} +for (var i = 0; i < 5; i++) + writeObject(o, {toString: function() { return "helo"} }, "helo"); +for (var i = 0; i < 5; i++) + writeObject(o, null, "null"); +for (var i = 0; i < 5; i++) + writeObject(o, "three", "three"); +for (var i = 0; i < 5; i++) + writeObject(o, 4.5, "4.5"); +for (var i = 0; i < 5; i++) { + try { + writeObject(o, undefined, ""); + } catch (e) { + assertEq(e instanceof TypeError, true); + } +} + +// Test writing various things to a string field. +function writeString(o, v, expected) { + o.f = v; + assertEq(typeof o.f, "string"); + assertEq("" + o.f, expected); +} +for (var i = 0; i < 5; i++) + writeString(s, {toString: function() { return "helo"} }, "helo"); +for (var i = 0; i < 5; i++) + writeString(s, null, "null"); +for (var i = 0; i < 5; i++) + writeString(s, "three", "three"); +for (var i = 0; i < 5; i++) + writeString(s, 4.5, "4.5"); +for (var i = 0; i < 5; i++) + writeString(s, undefined, "undefined"); + +// Test writing various things to a value field. +function writeValue(o, v, expectedType, expected) { + o.f = v; + assertEq(typeof o.f, expectedType); + assertEq("" + o.f, expected); +} +for (var i = 0; i < 5; i++) + writeValue(v, {toString: function() { return "helo"} }, "object", "helo"); +for (var i = 0; i < 5; i++) + writeValue(v, null, "object", "null"); +for (var i = 0; i < 5; i++) + writeValue(v, "three", "string", "three"); +for (var i = 0; i < 5; i++) + writeValue(v, 4.5, "number", "4.5"); +for (var i = 0; i < 5; i++) + writeValue(v, undefined, "undefined", "undefined"); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 13f076f0e11..0b67a45152a 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -1004,6 +1004,31 @@ CodeGenerator::visitValueToString(LValueToString *lir) return true; } +typedef JSObject *(*ToObjectFn)(JSContext *, HandleValue, bool); +static const VMFunction ToObjectInfo = FunctionInfo(ToObjectSlow); + +bool +CodeGenerator::visitValueToObjectOrNull(LValueToObjectOrNull *lir) +{ + ValueOperand input = ToValue(lir, LValueToObjectOrNull::Input); + Register output = ToRegister(lir->output()); + + OutOfLineCode *ool = oolCallVM(ToObjectInfo, lir, (ArgList(), input, Imm32(0)), + StoreRegisterTo(output)); + if (!ool) + return false; + + Label done; + masm.branchTestObject(Assembler::Equal, input, &done); + masm.branchTestNull(Assembler::NotEqual, input, ool->entry()); + + masm.bind(&done); + masm.unboxNonDouble(input, output); + + masm.bind(ool->rejoin()); + return true; +} + typedef JSObject *(*CloneRegExpObjectFn)(JSContext *, JSObject *); static const VMFunction CloneRegExpObjectInfo = FunctionInfo(CloneRegExpObject); @@ -3919,12 +3944,14 @@ CodeGenerator::emitObjectOrStringResultChecks(LInstruction *lir, MDefinition *mi if (!branchIfInvalidated(temp, &done)) return false; - if (mir->type() == MIRType_Object && + if ((mir->type() == MIRType_Object || mir->type() == MIRType_ObjectOrNull) && mir->resultTypeSet() && !mir->resultTypeSet()->unknownObject()) { // We have a result TypeSet, assert this object is in it. Label miss, ok; + if (mir->type() == MIRType_ObjectOrNull) + masm.branchPtr(Assembler::NotEqual, output, ImmWord(0), &ok); if (mir->resultTypeSet()->getObjectCount() > 0) masm.guardObjectType(output, mir->resultTypeSet(), temp, &miss); else @@ -3944,11 +3971,26 @@ CodeGenerator::emitObjectOrStringResultChecks(LInstruction *lir, MDefinition *mi masm.loadJSContext(temp); masm.passABIArg(temp); masm.passABIArg(output); - masm.callWithABINoProfiling(mir->type() == MIRType_Object - ? JS_FUNC_TO_DATA_PTR(void *, AssertValidObjectPtr) - : mir->type() == MIRType_String - ? JS_FUNC_TO_DATA_PTR(void *, AssertValidStringPtr) - : JS_FUNC_TO_DATA_PTR(void *, AssertValidSymbolPtr)); + + void *callee; + switch (mir->type()) { + case MIRType_Object: + callee = JS_FUNC_TO_DATA_PTR(void *, AssertValidObjectPtr); + break; + case MIRType_ObjectOrNull: + callee = JS_FUNC_TO_DATA_PTR(void *, AssertValidObjectOrNullPtr); + break; + case MIRType_String: + callee = JS_FUNC_TO_DATA_PTR(void *, AssertValidStringPtr); + break; + case MIRType_Symbol: + callee = JS_FUNC_TO_DATA_PTR(void *, AssertValidSymbolPtr); + break; + default: + MOZ_CRASH(); + } + + masm.callWithABINoProfiling(callee); restoreVolatile(); } @@ -4028,6 +4070,7 @@ CodeGenerator::emitDebugResultChecks(LInstruction *ins) switch (mir->type()) { case MIRType_Object: + case MIRType_ObjectOrNull: case MIRType_String: case MIRType_Symbol: return emitObjectOrStringResultChecks(ins, mir); @@ -6789,6 +6832,45 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole *ool) return true; } +template +static void +StoreUnboxedPointer(MacroAssembler &masm, T address, MIRType type, const LAllocation *value) +{ + masm.patchableCallPreBarrier(address, type); + if (value->isConstant()) { + Value v = *value->toConstant(); + if (v.isMarkable()) { + masm.storePtr(ImmGCPtr(v.toGCThing()), address); + } else { + MOZ_ASSERT(v.isNull()); + masm.storePtr(ImmWord(0), address); + } + } else { + masm.storePtr(ToRegister(value), address); + } +} + +bool +CodeGenerator::visitStoreUnboxedPointer(LStoreUnboxedPointer *lir) +{ + MOZ_ASSERT(lir->mir()->isStoreUnboxedObjectOrNull() || lir->mir()->isStoreUnboxedString()); + MIRType type = lir->mir()->isStoreUnboxedObjectOrNull() ? MIRType_Object : MIRType_String; + + Register elements = ToRegister(lir->elements()); + const LAllocation *index = lir->index(); + const LAllocation *value = lir->value(); + + if (index->isConstant()) { + Address address(elements, ToInt32(index) * sizeof(uintptr_t)); + StoreUnboxedPointer(masm, address, type, value); + } else { + BaseIndex address(elements, ToRegister(index), ScalePointer); + StoreUnboxedPointer(masm, address, type, value); + } + + return true; +} + typedef bool (*ArrayPopShiftFn)(JSContext *, HandleObject, MutableHandleValue); static const VMFunction ArrayPopDenseInfo = FunctionInfo(jit::ArrayPopDense); static const VMFunction ArrayShiftDenseInfo = FunctionInfo(jit::ArrayShiftDense); diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index ce3db386734..feaab4cd889 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -100,6 +100,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitIntToString(LIntToString *lir); bool visitDoubleToString(LDoubleToString *lir); bool visitValueToString(LValueToString *lir); + bool visitValueToObjectOrNull(LValueToObjectOrNull *lir); bool visitInteger(LInteger *lir); bool visitRegExp(LRegExp *lir); bool visitRegExpExec(LRegExpExec *lir); @@ -252,6 +253,7 @@ class CodeGenerator : public CodeGeneratorSpecific bool visitStoreElementV(LStoreElementV *lir); bool visitStoreElementHoleT(LStoreElementHoleT *lir); bool visitStoreElementHoleV(LStoreElementHoleV *lir); + bool visitStoreUnboxedPointer(LStoreUnboxedPointer *lir); bool emitArrayPopShift(LInstruction *lir, const MArrayPopShift *mir, Register obj, Register elementsTemp, Register lengthTemp, TypedOrValueRegister out); bool visitArrayPopShiftV(LArrayPopShiftV *lir); diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 5b388c0e2da..a7b73bf76fa 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -281,6 +281,11 @@ JitRuntime::initialize(JSContext *cx) if (!stringPreBarrier_) return false; + JitSpew(JitSpew_Codegen, "# Emitting Pre Barrier for Object"); + objectPreBarrier_ = generatePreBarrier(cx, MIRType_Object); + if (!objectPreBarrier_) + return false; + JitSpew(JitSpew_Codegen, "# Emitting Pre Barrier for Shape"); shapePreBarrier_ = generatePreBarrier(cx, MIRType_Shape); if (!shapePreBarrier_) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index b31a236c3f0..10199a45682 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -8152,11 +8152,8 @@ IonBuilder::setElemTryTypedObject(bool *emitted, MDefinition *obj, return true; case type::Reference: - case type::Struct: - case type::SizedArray: - case type::UnsizedArray: - // For now, only optimize storing scalars. - return true; + return setElemTryReferenceElemOfTypedObject(emitted, obj, index, + objPrediction, value, elemPrediction); case type::Scalar: return setElemTryScalarElemOfTypedObject(emitted, @@ -8166,11 +8163,41 @@ IonBuilder::setElemTryTypedObject(bool *emitted, MDefinition *obj, value, elemPrediction, elemSize); + + case type::Struct: + case type::SizedArray: + case type::UnsizedArray: + // Not yet optimized. + return true; } MOZ_CRASH("Bad kind"); } +bool +IonBuilder::setElemTryReferenceElemOfTypedObject(bool *emitted, + MDefinition *obj, + MDefinition *index, + TypedObjectPrediction objPrediction, + MDefinition *value, + TypedObjectPrediction elemPrediction) +{ + ReferenceTypeDescr::Type elemType = elemPrediction.referenceType(); + size_t elemSize = ReferenceTypeDescr::size(elemType); + + MDefinition *indexAsByteOffset; + if (!checkTypedObjectIndexInBounds(elemSize, obj, index, objPrediction, &indexAsByteOffset)) + return true; + + if (!storeReferenceTypedObjectValue(obj, indexAsByteOffset, elemType, value)) + return false; + + current->push(value); + + *emitted = true; + return true; +} + bool IonBuilder::setElemTryScalarElemOfTypedObject(bool *emitted, MDefinition *obj, @@ -10047,20 +10074,44 @@ IonBuilder::setPropTryTypedObject(bool *emitted, MDefinition *obj, return true; case type::Reference: - case type::Struct: - case type::SizedArray: - case type::UnsizedArray: - // For now, only optimize storing scalars. - return true; + return setPropTryReferencePropOfTypedObject(emitted, obj, fieldOffset, + value, fieldPrediction); case type::Scalar: return setPropTryScalarPropOfTypedObject(emitted, obj, fieldOffset, value, fieldPrediction); + + case type::Struct: + case type::SizedArray: + case type::UnsizedArray: + return true; } MOZ_CRASH("Unknown kind"); } +bool +IonBuilder::setPropTryReferencePropOfTypedObject(bool *emitted, + MDefinition *obj, + int32_t fieldOffset, + MDefinition *value, + TypedObjectPrediction fieldPrediction) +{ + ReferenceTypeDescr::Type fieldType = fieldPrediction.referenceType(); + + types::TypeObjectKey *globalType = types::TypeObjectKey::get(&script()->global()); + if (globalType->hasFlags(constraints(), types::OBJECT_FLAG_TYPED_OBJECT_NEUTERED)) + return true; + + if (!storeReferenceTypedObjectValue(obj, constantInt(fieldOffset), fieldType, value)) + return false; + + current->push(value); + + *emitted = true; + return true; +} + bool IonBuilder::setPropTryScalarPropOfTypedObject(bool *emitted, MDefinition *obj, @@ -11197,6 +11248,37 @@ IonBuilder::storeScalarTypedObjectValue(MDefinition *typedObj, return true; } +bool +IonBuilder::storeReferenceTypedObjectValue(MDefinition *typedObj, + MDefinition *byteOffset, + ReferenceTypeDescr::Type type, + MDefinition *value) +{ + if (NeedsPostBarrier(info(), value)) + current->add(MPostWriteBarrier::New(alloc(), typedObj, value)); + + // Find location within the owner object. + MDefinition *elements, *scaledOffset; + size_t alignment = ReferenceTypeDescr::alignment(type); + loadTypedObjectElements(typedObj, byteOffset, alignment, &elements, &scaledOffset); + + MInstruction *store; + switch (type) { + case ReferenceTypeDescr::TYPE_ANY: + store = MStoreElement::New(alloc(), elements, scaledOffset, value, false); + break; + case ReferenceTypeDescr::TYPE_OBJECT: + store = MStoreUnboxedObjectOrNull::New(alloc(), elements, scaledOffset, value); + break; + case ReferenceTypeDescr::TYPE_STRING: + store = MStoreUnboxedString::New(alloc(), elements, scaledOffset, value); + break; + } + + current->add(store); + return true; +} + MConstant * IonBuilder::constant(const Value &v) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 277e780b8db..287684ddb98 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -451,6 +451,11 @@ class IonBuilder types::TemporaryTypeSet *objTypes); bool setPropTryTypedObject(bool *emitted, MDefinition *obj, PropertyName *name, MDefinition *value); + bool setPropTryReferencePropOfTypedObject(bool *emitted, + MDefinition *obj, + int32_t fieldOffset, + MDefinition *value, + TypedObjectPrediction fieldPrediction); bool setPropTryScalarPropOfTypedObject(bool *emitted, MDefinition *obj, int32_t fieldOffset, @@ -481,6 +486,10 @@ class IonBuilder MDefinition *typeObjectForElementFromArrayStructType(MDefinition *typedObj); MDefinition *typeObjectForFieldFromStructType(MDefinition *type, size_t fieldIndex); + bool storeReferenceTypedObjectValue(MDefinition *typedObj, + MDefinition *byteOffset, + ReferenceTypeDescr::Type type, + MDefinition *value); bool storeScalarTypedObjectValue(MDefinition *typedObj, MDefinition *offset, ScalarTypeDescr::Type type, @@ -515,6 +524,12 @@ class IonBuilder MDefinition *index, MDefinition *value); bool setElemTryCache(bool *emitted, MDefinition *object, MDefinition *index, MDefinition *value); + bool setElemTryReferenceElemOfTypedObject(bool *emitted, + MDefinition *obj, + MDefinition *index, + TypedObjectPrediction objPrediction, + MDefinition *value, + TypedObjectPrediction elemPrediction); bool setElemTryScalarElemOfTypedObject(bool *emitted, MDefinition *obj, MDefinition *index, diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index 6b8e15061cc..eb58be78d17 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -369,6 +369,7 @@ enum MIRType MIRType_MagicIsConstructing, // JS_IS_CONSTRUCTING magic value. MIRType_MagicUninitializedLexical, // JS_UNINITIALIZED_LEXICAL magic value. MIRType_Value, + MIRType_ObjectOrNull, MIRType_None, // Invalid, used as a placeholder. MIRType_Slots, // A slots vector MIRType_Elements, // An elements vector diff --git a/js/src/jit/JitCompartment.h b/js/src/jit/JitCompartment.h index 3024bc21d5c..71837389688 100644 --- a/js/src/jit/JitCompartment.h +++ b/js/src/jit/JitCompartment.h @@ -187,6 +187,7 @@ class JitRuntime // Thunk that calls the GC pre barrier. JitCode *valuePreBarrier_; JitCode *stringPreBarrier_; + JitCode *objectPreBarrier_; JitCode *shapePreBarrier_; JitCode *typeObjectPreBarrier_; @@ -361,6 +362,7 @@ class JitRuntime switch (type) { case MIRType_Value: return valuePreBarrier_; case MIRType_String: return stringPreBarrier_; + case MIRType_Object: return objectPreBarrier_; case MIRType_Shape: return shapePreBarrier_; case MIRType_TypeObject: return typeObjectPreBarrier_; default: MOZ_CRASH(); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 10bf998cfdc..42286a11f27 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -3715,6 +3715,22 @@ class LValueToString : public LInstructionHelper<1, BOX_PIECES, 1> } }; +// Convert a value to an object or null pointer. +class LValueToObjectOrNull : public LInstructionHelper<1, BOX_PIECES, 0> +{ + public: + LIR_HEADER(ValueToObjectOrNull) + + explicit LValueToObjectOrNull() + {} + + static const size_t Input = 0; + + const MToObjectOrNull *mir() { + return mir_->toToObjectOrNull(); + } +}; + class LInt32x4ToFloat32x4 : public LInstructionHelper<1, 1, 0> { public: @@ -4608,6 +4624,33 @@ class LStoreElementHoleT : public LInstructionHelper<0, 4, 0> } }; +class LStoreUnboxedPointer : public LInstructionHelper<0, 3, 0> +{ + public: + LIR_HEADER(StoreUnboxedPointer) + + LStoreUnboxedPointer(LAllocation elements, LAllocation index, LAllocation value) { + setOperand(0, elements); + setOperand(1, index); + setOperand(2, value); + } + + MDefinition *mir() { + MOZ_ASSERT(mir_->isStoreUnboxedObjectOrNull() || mir_->isStoreUnboxedString()); + return mir_; + } + + const LAllocation *elements() { + return getOperand(0); + } + const LAllocation *index() { + return getOperand(1); + } + const LAllocation *value() { + return getOperand(2); + } +}; + class LArrayPopShiftV : public LInstructionHelper { public: diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index 4b78a33b468..e83dd4e24c7 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -554,6 +554,7 @@ class LDefinition case MIRType_String: case MIRType_Symbol: case MIRType_Object: + case MIRType_ObjectOrNull: return LDefinition::OBJECT; case MIRType_Double: return LDefinition::DOUBLE; diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index 34ce62630ea..787ed80b215 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -173,6 +173,7 @@ _(IntToString) \ _(DoubleToString) \ _(ValueToString) \ + _(ValueToObjectOrNull) \ _(Int32x4ToFloat32x4) \ _(Float32x4ToInt32x4) \ _(Start) \ @@ -221,6 +222,7 @@ _(LoadElementHole) \ _(StoreElementV) \ _(StoreElementT) \ + _(StoreUnboxedPointer) \ _(ArrayPopShiftV) \ _(ArrayPopShiftT) \ _(ArrayPushV) \ diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 9c5fa6d7b1e..6787bfb23f0 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -2038,6 +2038,17 @@ LIRGenerator::visitToString(MToString *ins) } } +bool +LIRGenerator::visitToObjectOrNull(MToObjectOrNull *ins) +{ + MOZ_ASSERT(ins->input()->type() == MIRType_Value); + + LValueToObjectOrNull *lir = new(alloc()) LValueToObjectOrNull(); + if (!useBox(lir, LValueToString::Input, ins->input())) + return false; + return define(lir, ins) && assignSafepoint(lir, ins); +} + static bool MustCloneRegExpForCall(MCall *call, uint32_t useIndex) { @@ -2774,6 +2785,38 @@ LIRGenerator::visitStoreElementHole(MStoreElementHole *ins) return add(lir, ins) && assignSafepoint(lir, ins); } +bool +LIRGenerator::visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull *ins) +{ + MOZ_ASSERT(ins->elements()->type() == MIRType_Elements); + MOZ_ASSERT(ins->index()->type() == MIRType_Int32); + MOZ_ASSERT(ins->value()->type() == MIRType_Object || + ins->value()->type() == MIRType_Null || + ins->value()->type() == MIRType_ObjectOrNull); + + const LUse elements = useRegister(ins->elements()); + const LAllocation index = useRegisterOrNonDoubleConstant(ins->index()); + const LAllocation value = useRegisterOrNonDoubleConstant(ins->value()); + + LInstruction *lir = new(alloc()) LStoreUnboxedPointer(elements, index, value); + return add(lir, ins); +} + +bool +LIRGenerator::visitStoreUnboxedString(MStoreUnboxedString *ins) +{ + MOZ_ASSERT(ins->elements()->type() == MIRType_Elements); + MOZ_ASSERT(ins->index()->type() == MIRType_Int32); + MOZ_ASSERT(ins->value()->type() == MIRType_String); + + const LUse elements = useRegister(ins->elements()); + const LAllocation index = useRegisterOrConstant(ins->index()); + const LAllocation value = useRegisterOrNonDoubleConstant(ins->value()); + + LInstruction *lir = new(alloc()) LStoreUnboxedPointer(elements, index, value); + return add(lir, ins); +} + bool LIRGenerator::visitEffectiveAddress(MEffectiveAddress *ins) { diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index dd3667210e8..aefc37e503d 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -159,6 +159,7 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitToInt32(MToInt32 *convert); bool visitTruncateToInt32(MTruncateToInt32 *truncate); bool visitToString(MToString *convert); + bool visitToObjectOrNull(MToObjectOrNull *convert); bool visitRegExp(MRegExp *ins); bool visitRegExpExec(MRegExpExec *ins); bool visitRegExpTest(MRegExpTest *ins); @@ -202,6 +203,8 @@ class LIRGenerator : public LIRGeneratorSpecific bool visitLoadElementHole(MLoadElementHole *ins); bool visitStoreElement(MStoreElement *ins); bool visitStoreElementHole(MStoreElementHole *ins); + bool visitStoreUnboxedObjectOrNull(MStoreUnboxedObjectOrNull *ins); + bool visitStoreUnboxedString(MStoreUnboxedString *ins); bool visitEffectiveAddress(MEffectiveAddress *ins); bool visitArrayPopShift(MArrayPopShift *ins); bool visitArrayPush(MArrayPush *ins); diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index e19cce4249c..2f6dec55d70 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -268,10 +268,12 @@ class AliasSet { enum Flag { None_ = 0, ObjectFields = 1 << 0, // shape, class, slots, length etc. - Element = 1 << 1, // A member of obj->elements. + Element = 1 << 1, // A member of obj->elements, or reference + // typed object field. DynamicSlot = 1 << 2, // A member of obj->slots. FixedSlot = 1 << 3, // A member of obj->fixedSlots(). - TypedArrayElement = 1 << 4, // A typed array element. + TypedArrayElement = 1 << 4, // A typed array element, or scalar typed + // object field. DOMProperty = 1 << 5, // A DOM property FrameArgument = 1 << 6, // An argument kept on the stack frame AsmJSGlobalVar = 1 << 7, // An asm.js global var @@ -4642,6 +4644,36 @@ class MToString : ALLOW_CLONE(MToString) }; +// Converts any type to an object or null value, throwing on undefined. +class MToObjectOrNull : + public MUnaryInstruction, + public BoxInputsPolicy::Data +{ + explicit MToObjectOrNull(MDefinition *def) + : MUnaryInstruction(def) + { + setResultType(MIRType_ObjectOrNull); + setMovable(); + } + + public: + INSTRUCTION_HEADER(ToObjectOrNull) + static MToObjectOrNull *New(TempAllocator &alloc, MDefinition *def) + { + return new(alloc) MToObjectOrNull(def); + } + + bool congruentTo(const MDefinition *ins) const { + return congruentIfOperandsEqual(ins); + } + + AliasSet getAliasSet() const { + return AliasSet::None(); + } + + ALLOW_CLONE(MToObjectOrNull) +}; + class MBitNot : public MUnaryInstruction, public BitwisePolicy::Data @@ -7966,6 +7998,82 @@ class MStoreElementHole ALLOW_CLONE(MStoreElementHole) }; +// Store an unboxed object or null pointer to a vector. +class MStoreUnboxedObjectOrNull + : public MAryInstruction<3>, + public ConvertToObjectOrNullPolicy<2>::Data +{ + MStoreUnboxedObjectOrNull(MDefinition *elements, MDefinition *index, MDefinition *value) { + initOperand(0, elements); + initOperand(1, index); + initOperand(2, value); + MOZ_ASSERT(elements->type() == MIRType_Elements); + MOZ_ASSERT(index->type() == MIRType_Int32); + } + + public: + INSTRUCTION_HEADER(StoreUnboxedObjectOrNull) + + static MStoreUnboxedObjectOrNull *New(TempAllocator &alloc, + MDefinition *elements, MDefinition *index, + MDefinition *value) { + return new(alloc) MStoreUnboxedObjectOrNull(elements, index, value); + } + MDefinition *elements() const { + return getOperand(0); + } + MDefinition *index() const { + return getOperand(1); + } + MDefinition *value() const { + return getOperand(2); + } + AliasSet getAliasSet() const { + // Use AliasSet::Element for reference typed object fields. + return AliasSet::Store(AliasSet::Element); + } + + ALLOW_CLONE(MStoreUnboxedObjectOrNull) +}; + +// Store an unboxed object or null pointer to a vector. +class MStoreUnboxedString + : public MAryInstruction<3>, + public ConvertToStringPolicy<2>::Data +{ + MStoreUnboxedString(MDefinition *elements, MDefinition *index, MDefinition *value) { + initOperand(0, elements); + initOperand(1, index); + initOperand(2, value); + MOZ_ASSERT(elements->type() == MIRType_Elements); + MOZ_ASSERT(index->type() == MIRType_Int32); + } + + public: + INSTRUCTION_HEADER(StoreUnboxedString) + + static MStoreUnboxedString *New(TempAllocator &alloc, + MDefinition *elements, MDefinition *index, + MDefinition *value) { + return new(alloc) MStoreUnboxedString(elements, index, value); + } + MDefinition *elements() const { + return getOperand(0); + } + MDefinition *index() const { + return getOperand(1); + } + MDefinition *value() const { + return getOperand(2); + } + AliasSet getAliasSet() const { + // Use AliasSet::Element for reference typed object fields. + return AliasSet::Store(AliasSet::Element); + } + + ALLOW_CLONE(MStoreUnboxedString) +}; + // Array.prototype.pop or Array.prototype.shift on a dense array. class MArrayPopShift : public MUnaryInstruction, diff --git a/js/src/jit/MOpcodes.h b/js/src/jit/MOpcodes.h index 3d5ea4cad87..5d645ac6deb 100644 --- a/js/src/jit/MOpcodes.h +++ b/js/src/jit/MOpcodes.h @@ -108,6 +108,7 @@ namespace jit { _(ToInt32) \ _(TruncateToInt32) \ _(ToString) \ + _(ToObjectOrNull) \ _(NewArray) \ _(NewArrayCopyOnWrite) \ _(NewObject) \ @@ -176,6 +177,8 @@ namespace jit { _(LoadElementHole) \ _(StoreElement) \ _(StoreElementHole) \ + _(StoreUnboxedObjectOrNull) \ + _(StoreUnboxedString) \ _(ArrayPopShift) \ _(ArrayPush) \ _(ArrayConcat) \ diff --git a/js/src/jit/ParallelSafetyAnalysis.cpp b/js/src/jit/ParallelSafetyAnalysis.cpp index 96d335dbb35..ed9f265575c 100644 --- a/js/src/jit/ParallelSafetyAnalysis.cpp +++ b/js/src/jit/ParallelSafetyAnalysis.cpp @@ -200,6 +200,7 @@ class ParallelSafetyVisitor : public MDefinitionVisitor SAFE_OP(TruncateToInt32) SAFE_OP(MaybeToDoubleElement) CUSTOM_OP(ToString) + UNSAFE_OP(ToObjectOrNull) CUSTOM_OP(NewArray) UNSAFE_OP(NewArrayCopyOnWrite) UNSAFE_OP(NewTypedObject) @@ -260,6 +261,8 @@ class ParallelSafetyVisitor : public MDefinitionVisitor SAFE_OP(LoadElementHole) MAYBE_WRITE_GUARDED_OP(StoreElement, elements) WRITE_GUARDED_OP(StoreElementHole, elements) + UNSAFE_OP(StoreUnboxedObjectOrNull) + UNSAFE_OP(StoreUnboxedString) UNSAFE_OP(ArrayPopShift) UNSAFE_OP(ArrayPush) SAFE_OP(LoadTypedArrayElement) diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index a704087e4aa..bd7648959d6 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -405,6 +405,32 @@ ConvertToStringPolicy::staticAdjustInputs(TempAllocator &alloc, MInstruction template bool ConvertToStringPolicy<0>::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins); template bool ConvertToStringPolicy<1>::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins); +template bool ConvertToStringPolicy<2>::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins); + +template +bool +ConvertToObjectOrNullPolicy::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins) +{ + MDefinition *in = ins->getOperand(Op); + if (in->type() == MIRType_Object || + in->type() == MIRType_Null || + in->type() == MIRType_ObjectOrNull) + { + return true; + } + + MToObjectOrNull *replace = MToObjectOrNull::New(alloc, in); + ins->block()->insertBefore(ins, replace); + ins->replaceOperand(Op, replace); + + if (!BoxPolicy<0>::staticAdjustInputs(alloc, replace)) + return false; + + return true; +} + +template bool ConvertToObjectOrNullPolicy<2>::staticAdjustInputs(TempAllocator &alloc, + MInstruction *ins); template bool @@ -941,6 +967,8 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator &alloc, MInstruction *ins) _(BoxPolicy<0>) \ _(ConvertToInt32Policy<0>) \ _(ConvertToStringPolicy<0>) \ + _(ConvertToStringPolicy<2>) \ + _(ConvertToObjectOrNullPolicy<2>) \ _(DoublePolicy<0>) \ _(FloatingPointPolicy<0>) \ _(IntPolicy<0>) \ diff --git a/js/src/jit/TypePolicy.h b/js/src/jit/TypePolicy.h index 2c8ce6a947a..d04ea5f40a0 100644 --- a/js/src/jit/TypePolicy.h +++ b/js/src/jit/TypePolicy.h @@ -148,6 +148,19 @@ class ConvertToStringPolicy : public TypePolicy } }; +// Expect an object or null value for operand Op. Else a ToObjectOrNull +// instruction is inserted. +template +class ConvertToObjectOrNullPolicy : public TypePolicy +{ + public: + EMPTY_DATA_; + static bool staticAdjustInputs(TempAllocator &alloc, MInstruction *def); + bool adjustInputs(TempAllocator &alloc, MInstruction *def) { + return staticAdjustInputs(alloc, def); + } +}; + // Expect an Int for operand Op. If the input is a Value, it is unboxed. template class IntPolicy : public BoxInputsPolicy diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 8536916d1e5..f599bbe3a51 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1147,6 +1147,13 @@ AssertValidObjectPtr(JSContext *cx, JSObject *obj) } } +void +AssertValidObjectOrNullPtr(JSContext *cx, JSObject *obj) +{ + if (obj) + AssertValidObjectPtr(cx, obj); +} + void AssertValidStringPtr(JSContext *cx, JSString *str) { @@ -1235,6 +1242,13 @@ MarkStringFromIon(JSRuntime *rt, JSString **stringp) gc::MarkStringUnbarriered(&rt->gc.marker, stringp, "write barrier"); } +void +MarkObjectFromIon(JSRuntime *rt, JSObject **objp) +{ + if (*objp) + gc::MarkObjectUnbarriered(&rt->gc.marker, objp, "write barrier"); +} + void MarkShapeFromIon(JSRuntime *rt, Shape **shapep) { diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 5ed78b34c1d..6523e07cf57 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -745,6 +745,7 @@ bool SetDenseElement(JSContext *cx, HandleNativeObject obj, int32_t index, Handl #ifdef DEBUG void AssertValidObjectPtr(JSContext *cx, JSObject *obj); +void AssertValidObjectOrNullPtr(JSContext *cx, JSObject *obj); void AssertValidStringPtr(JSContext *cx, JSString *str); void AssertValidSymbolPtr(JSContext *cx, JS::Symbol *sym); void AssertValidValue(JSContext *cx, Value *v); @@ -754,6 +755,7 @@ JSObject *TypedObjectProto(JSObject *obj); void MarkValueFromIon(JSRuntime *rt, Value *vp); void MarkStringFromIon(JSRuntime *rt, JSString **stringp); +void MarkObjectFromIon(JSRuntime *rt, JSObject **objp); void MarkShapeFromIon(JSRuntime *rt, Shape **shapep); void MarkTypeObjectFromIon(JSRuntime *rt, types::TypeObject **typep); @@ -766,6 +768,8 @@ IonMarkFunction(MIRType type) return JS_FUNC_TO_DATA_PTR(void *, MarkValueFromIon); case MIRType_String: return JS_FUNC_TO_DATA_PTR(void *, MarkStringFromIon); + case MIRType_Object: + return JS_FUNC_TO_DATA_PTR(void *, MarkObjectFromIon); case MIRType_Shape: return JS_FUNC_TO_DATA_PTR(void *, MarkShapeFromIon); case MIRType_TypeObject: diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index c17c3c01452..d53fe8f49eb 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -2503,26 +2503,38 @@ MacroAssemblerARMCompat::store32_NoSecondScratch(Imm32 src, const Address &addre storePtr(ScratchRegister, address); } +template void -MacroAssemblerARMCompat::storePtr(ImmWord imm, const Address &address) +MacroAssemblerARMCompat::storePtr(ImmWord imm, T address) { movePtr(imm, ScratchRegister); storePtr(ScratchRegister, address); } +template void MacroAssemblerARMCompat::storePtr
(ImmWord imm, Address address); +template void MacroAssemblerARMCompat::storePtr(ImmWord imm, BaseIndex address); + +template void -MacroAssemblerARMCompat::storePtr(ImmPtr imm, const Address &address) +MacroAssemblerARMCompat::storePtr(ImmPtr imm, T address) { storePtr(ImmWord(uintptr_t(imm.value)), address); } +template void MacroAssemblerARMCompat::storePtr
(ImmPtr imm, Address address); +template void MacroAssemblerARMCompat::storePtr(ImmPtr imm, BaseIndex address); + +template void -MacroAssemblerARMCompat::storePtr(ImmGCPtr imm, const Address &address) +MacroAssemblerARMCompat::storePtr(ImmGCPtr imm, T address) { movePtr(imm, ScratchRegister); storePtr(ScratchRegister, address); } +template void MacroAssemblerARMCompat::storePtr
(ImmGCPtr imm, Address address); +template void MacroAssemblerARMCompat::storePtr(ImmGCPtr imm, BaseIndex address); + void MacroAssemblerARMCompat::storePtr(Register src, const Address &address) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 242f0ad92a0..7fbf7b115c6 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1396,9 +1396,9 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void store32_NoSecondScratch(Imm32 src, const Address &address); - void storePtr(ImmWord imm, const Address &address); - void storePtr(ImmPtr imm, const Address &address); - void storePtr(ImmGCPtr imm, const Address &address); + template void storePtr(ImmWord imm, T address); + template void storePtr(ImmPtr imm, T address); + template void storePtr(ImmGCPtr imm, T address); void storePtr(Register src, const Address &address); void storePtr(Register src, const BaseIndex &address); void storePtr(Register src, AbsoluteAddress dest); diff --git a/js/src/jit/mips/MacroAssembler-mips.cpp b/js/src/jit/mips/MacroAssembler-mips.cpp index 4a441503aed..121b028f4ab 100644 --- a/js/src/jit/mips/MacroAssembler-mips.cpp +++ b/js/src/jit/mips/MacroAssembler-mips.cpp @@ -2043,26 +2043,38 @@ MacroAssemblerMIPSCompat::store32(Register src, const BaseIndex &dest) ma_store(src, dest, SizeWord); } +template void -MacroAssemblerMIPSCompat::storePtr(ImmWord imm, const Address &address) +MacroAssemblerMIPSCompat::storePtr(ImmWord imm, T address) { ma_li(ScratchRegister, Imm32(imm.value)); ma_sw(ScratchRegister, address); } +template void MacroAssemblerMIPSCompat::storePtr
(ImmWord imm, Address address); +template void MacroAssemblerMIPSCompat::storePtr(ImmWord imm, BaseIndex address); + +template void -MacroAssemblerMIPSCompat::storePtr(ImmPtr imm, const Address &address) +MacroAssemblerMIPSCompat::storePtr(ImmPtr imm, T address) { storePtr(ImmWord(uintptr_t(imm.value)), address); } +template void MacroAssemblerMIPSCompat::storePtr
(ImmPtr imm, Address address); +template void MacroAssemblerMIPSCompat::storePtr(ImmPtr imm, BaseIndex address); + +template void -MacroAssemblerMIPSCompat::storePtr(ImmGCPtr imm, const Address &address) +MacroAssemblerMIPSCompat::storePtr(ImmGCPtr imm, T address) { ma_li(ScratchRegister, imm); ma_sw(ScratchRegister, address); } +template void MacroAssemblerMIPSCompat::storePtr
(ImmGCPtr imm, Address address); +template void MacroAssemblerMIPSCompat::storePtr(ImmGCPtr imm, BaseIndex address); + void MacroAssemblerMIPSCompat::storePtr(Register src, const Address &address) { diff --git a/js/src/jit/mips/MacroAssembler-mips.h b/js/src/jit/mips/MacroAssembler-mips.h index 3748a5a1a8e..07f1785f286 100644 --- a/js/src/jit/mips/MacroAssembler-mips.h +++ b/js/src/jit/mips/MacroAssembler-mips.h @@ -1263,9 +1263,9 @@ public: store32(src, address); } - void storePtr(ImmWord imm, const Address &address); - void storePtr(ImmPtr imm, const Address &address); - void storePtr(ImmGCPtr imm, const Address &address); + template void storePtr(ImmWord imm, T address); + template void storePtr(ImmPtr imm, T address); + template void storePtr(ImmGCPtr imm, T address); void storePtr(Register src, const Address &address); void storePtr(Register src, const BaseIndex &address); void storePtr(Register src, AbsoluteAddress dest); diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 215930edb7e..10791bfb5cb 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -766,7 +766,8 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared load32(Address(ScratchReg, 0x0), dest); } } - void storePtr(ImmWord imm, const Address &address) { + template + void storePtr(ImmWord imm, T address) { if ((intptr_t)imm.value <= INT32_MAX && (intptr_t)imm.value >= INT32_MIN) { movq(Imm32((int32_t)imm.value), Operand(address)); } else { @@ -774,10 +775,12 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared movq(ScratchReg, Operand(address)); } } - void storePtr(ImmPtr imm, const Address &address) { + template + void storePtr(ImmPtr imm, T address) { storePtr(ImmWord(uintptr_t(imm.value)), address); } - void storePtr(ImmGCPtr imm, const Address &address) { + template + void storePtr(ImmGCPtr imm, T address) { movq(imm, ScratchReg); movq(ScratchReg, Operand(address)); } diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index 515c8696087..aa225b7629d 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -725,13 +725,16 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void load32(AbsoluteAddress address, Register dest) { movl(Operand(address), dest); } - void storePtr(ImmWord imm, const Address &address) { + template + void storePtr(ImmWord imm, T address) { movl(Imm32(imm.value), Operand(address)); } - void storePtr(ImmPtr imm, const Address &address) { + template + void storePtr(ImmPtr imm, T address) { storePtr(ImmWord(uintptr_t(imm.value)), address); } - void storePtr(ImmGCPtr imm, const Address &address) { + template + void storePtr(ImmGCPtr imm, T address) { movl(imm, Operand(address)); } void storePtr(Register src, const Address &address) { From 1c6b557ffb171f9640ccb4621d38a48aa3570bd7 Mon Sep 17 00:00:00 2001 From: Masayuki Nakano Date: Wed, 5 Nov 2014 10:36:37 +0900 Subject: [PATCH 042/153] Bug 1047588 IMEContentObserver::Init() should take nsIEditor because nsDocShell sometimes hasn't been initialized and nsContentEditor::GetHTMLEditor() fails r=smaug --- dom/events/IMEContentObserver.cpp | 29 +++++---------------- dom/events/IMEContentObserver.h | 2 +- dom/events/IMEStateManager.cpp | 30 ++++++++++++---------- dom/events/IMEStateManager.h | 9 ++++--- editor/libeditor/nsEditor.cpp | 4 +-- editor/libeditor/nsEditorEventListener.cpp | 3 ++- 6 files changed, 34 insertions(+), 43 deletions(-) diff --git a/dom/events/IMEContentObserver.cpp b/dom/events/IMEContentObserver.cpp index 5620004d9fe..836c6882abc 100644 --- a/dom/events/IMEContentObserver.cpp +++ b/dom/events/IMEContentObserver.cpp @@ -28,7 +28,6 @@ #include "nsISelectionController.h" #include "nsISelectionPrivate.h" #include "nsISupports.h" -#include "nsITextControlElement.h" #include "nsIWidget.h" #include "nsPresContext.h" #include "nsThreadUtils.h" @@ -99,8 +98,11 @@ IMEContentObserver::IMEContentObserver() void IMEContentObserver::Init(nsIWidget* aWidget, nsPresContext* aPresContext, - nsIContent* aContent) + nsIContent* aContent, + nsIEditor* aEditor) { + MOZ_ASSERT(aEditor, "aEditor must not be null"); + mESM = aPresContext->EventStateManager(); mESM->OnStartToObserveContent(this); @@ -110,27 +112,8 @@ IMEContentObserver::Init(nsIWidget* aWidget, return; } - nsCOMPtr textControlElement = - do_QueryInterface(mEditableNode); - if (textControlElement) { - // This may fail. For example, - mEditor = textControlElement->GetTextEditor(); - if (!mEditor && mEditableNode->IsContent()) { - // The element must be an editing host. - nsIContent* editingHost = mEditableNode->AsContent()->GetEditingHost(); - MOZ_ASSERT(editingHost == mEditableNode, - "found editing host should be mEditableNode"); - if (editingHost == mEditableNode) { - mEditor = nsContentUtils::GetHTMLEditor(aPresContext); - } - } - } else { - mEditor = nsContentUtils::GetHTMLEditor(aPresContext); - } - MOZ_ASSERT(mEditor, "Failed to get editor"); - if (mEditor) { - mEditor->AddEditorObserver(this); - } + mEditor = aEditor; + mEditor->AddEditorObserver(this); nsIPresShell* presShell = aPresContext->PresShell(); diff --git a/dom/events/IMEContentObserver.h b/dom/events/IMEContentObserver.h index f8dba04917f..43a67f5a022 100644 --- a/dom/events/IMEContentObserver.h +++ b/dom/events/IMEContentObserver.h @@ -64,7 +64,7 @@ public: WidgetMouseEvent* aMouseEvent); void Init(nsIWidget* aWidget, nsPresContext* aPresContext, - nsIContent* aContent); + nsIContent* aContent, nsIEditor* aEditor); void Destroy(); /** * IMEContentObserver is stored by EventStateManager during observing. diff --git a/dom/events/IMEStateManager.cpp b/dom/events/IMEStateManager.cpp index 663dd8baa2b..5e3722613f6 100644 --- a/dom/events/IMEStateManager.cpp +++ b/dom/events/IMEStateManager.cpp @@ -25,6 +25,7 @@ #include "nsIContent.h" #include "nsIDocument.h" #include "nsIDOMMouseEvent.h" +#include "nsIEditor.h" #include "nsIForm.h" #include "nsIFormControl.h" #include "nsINode.h" @@ -559,12 +560,14 @@ IMEStateManager::OnClickInEditor(nsPresContext* aPresContext, // static void IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext, - nsIContent* aContent) + nsIContent* aContent, + nsIEditor* aEditor) { PR_LOG(sISMLog, PR_LOG_ALWAYS, - ("ISM: IMEStateManager::OnFocusInEditor(aPresContext=0x%p, aContent=0x%p), " - "sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p", - aPresContext, aContent, sPresContext, sContent, + ("ISM: IMEStateManager::OnFocusInEditor(aPresContext=0x%p, aContent=0x%p, " + "aEditor=0x%p), sPresContext=0x%p, sContent=0x%p, " + "sActiveIMEContentObserver=0x%p", + aPresContext, aContent, aEditor, sPresContext, sContent, sActiveIMEContentObserver)); if (sPresContext != aPresContext || sContent != aContent) { @@ -586,21 +589,22 @@ IMEStateManager::OnFocusInEditor(nsPresContext* aPresContext, DestroyIMEContentObserver(); } - CreateIMEContentObserver(); + CreateIMEContentObserver(aEditor); } // static void IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, - nsIContent* aContent) + nsIContent* aContent, + nsIEditor* aEditor) { PR_LOG(sISMLog, PR_LOG_ALWAYS, ("ISM: IMEStateManager::UpdateIMEState(aNewIMEState={ mEnabled=%s, " - "mOpen=%s }, aContent=0x%p), " + "mOpen=%s }, aContent=0x%p, aEditor=0x%p), " "sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p, " "sIsGettingNewIMEState=%s", GetIMEStateEnabledName(aNewIMEState.mEnabled), - GetIMEStateSetOpenName(aNewIMEState.mOpen), aContent, + GetIMEStateSetOpenName(aNewIMEState.mOpen), aContent, aEditor, sPresContext, sContent, sActiveIMEContentObserver, GetBoolName(sIsGettingNewIMEState))); @@ -651,7 +655,7 @@ IMEStateManager::UpdateIMEState(const IMEState& aNewIMEState, } if (createTextStateManager) { - CreateIMEContentObserver(); + CreateIMEContentObserver(aEditor); } } @@ -1145,13 +1149,13 @@ IMEStateManager::DestroyIMEContentObserver() // static void -IMEStateManager::CreateIMEContentObserver() +IMEStateManager::CreateIMEContentObserver(nsIEditor* aEditor) { PR_LOG(sISMLog, PR_LOG_ALWAYS, - ("ISM: IMEStateManager::CreateIMEContentObserver(), " + ("ISM: IMEStateManager::CreateIMEContentObserver(aEditor=0x%p), " "sPresContext=0x%p, sContent=0x%p, sActiveIMEContentObserver=0x%p, " "sActiveIMEContentObserver->IsManaging(sPresContext, sContent)=%s", - sPresContext, sContent, sActiveIMEContentObserver, + aEditor, sPresContext, sContent, sActiveIMEContentObserver, GetBoolName(sActiveIMEContentObserver ? sActiveIMEContentObserver->IsManaging(sPresContext, sContent) : false))); @@ -1195,7 +1199,7 @@ IMEStateManager::CreateIMEContentObserver() // instance. So, sActiveIMEContentObserver would be replaced with new one. // We should hold the current instance here. nsRefPtr kungFuDeathGrip(sActiveIMEContentObserver); - sActiveIMEContentObserver->Init(widget, sPresContext, sContent); + sActiveIMEContentObserver->Init(widget, sPresContext, sContent, aEditor); } // static diff --git a/dom/events/IMEStateManager.h b/dom/events/IMEStateManager.h index 665edcc2d0f..01ad078ac38 100644 --- a/dom/events/IMEStateManager.h +++ b/dom/events/IMEStateManager.h @@ -11,6 +11,7 @@ class nsIContent; class nsIDOMMouseEvent; +class nsIEditor; class nsINode; class nsPIDOMWindow; class nsPresContext; @@ -65,7 +66,8 @@ public: // Note that this method changes the IME state of the active element in the // widget. So, the caller must have focus. static void UpdateIMEState(const IMEState &aNewIMEState, - nsIContent* aContent); + nsIContent* aContent, + nsIEditor* aEditor); // This method is called when user operates mouse button in focused editor // and before the editor handles it. @@ -89,7 +91,8 @@ public: // If the editor is for contenteditable, the active editinghost. // If the editor is for designMode, nullptr. static void OnFocusInEditor(nsPresContext* aPresContext, - nsIContent* aContent); + nsIContent* aContent, + nsIEditor* aEditor); /** * All composition events must be dispatched via DispatchCompositionEvent() @@ -149,7 +152,7 @@ protected: nsIContent* aContent); static void EnsureTextCompositionArray(); - static void CreateIMEContentObserver(); + static void CreateIMEContentObserver(nsIEditor* aEditor); static void DestroyIMEContentObserver(); static bool IsEditable(nsINode* node); diff --git a/editor/libeditor/nsEditor.cpp b/editor/libeditor/nsEditor.cpp index d4e7e5a77b3..75b8797c819 100644 --- a/editor/libeditor/nsEditor.cpp +++ b/editor/libeditor/nsEditor.cpp @@ -315,7 +315,7 @@ nsEditor::PostCreate() rv = GetPreferredIMEState(&newState); NS_ENSURE_SUCCESS(rv, NS_OK); nsCOMPtr content = GetFocusedContentForIME(); - IMEStateManager::UpdateIMEState(newState, content); + IMEStateManager::UpdateIMEState(newState, content, this); } return NS_OK; } @@ -496,7 +496,7 @@ nsEditor::SetFlags(uint32_t aFlags) // NOTE: When the enabled state isn't going to be modified, this method // is going to do nothing. nsCOMPtr content = GetFocusedContentForIME(); - IMEStateManager::UpdateIMEState(newState, content); + IMEStateManager::UpdateIMEState(newState, content, this); } } diff --git a/editor/libeditor/nsEditorEventListener.cpp b/editor/libeditor/nsEditorEventListener.cpp index 702a61862fb..01f6a4af7f8 100644 --- a/editor/libeditor/nsEditorEventListener.cpp +++ b/editor/libeditor/nsEditorEventListener.cpp @@ -1097,7 +1097,8 @@ nsEditorEventListener::Focus(nsIDOMEvent* aEvent) nsCOMPtr ps = GetPresShell(); NS_ENSURE_TRUE(ps, NS_OK); nsCOMPtr focusedContent = mEditor->GetFocusedContentForIME(); - IMEStateManager::OnFocusInEditor(ps->GetPresContext(), focusedContent); + IMEStateManager::OnFocusInEditor(ps->GetPresContext(), focusedContent, + mEditor); return NS_OK; } From edd6c20e5ba44a251b11423c39d5578ab971d7cc Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Tue, 4 Nov 2014 17:38:19 -0800 Subject: [PATCH 043/153] Bug 1092446 - [e10s] Allow unprivileged scopes to call content-to-chrome CPOWs (r=bholley) --- dom/base/test/chrome/cpows_child.js | 9 +++++++++ dom/base/test/chrome/cpows_parent.xul | 5 ++++- js/xpconnect/wrappers/WrapperFactory.cpp | 9 +++++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/dom/base/test/chrome/cpows_child.js b/dom/base/test/chrome/cpows_child.js index 95bdfb9e1f5..b81a24a6d66 100644 --- a/dom/base/test/chrome/cpows_child.js +++ b/dom/base/test/chrome/cpows_child.js @@ -87,6 +87,15 @@ function parent_test() addMessageListener("cpows:from_parent", (msg) => { let obj = msg.objects.obj; ok(obj.a == 1, "correct value from parent"); + + // Test that a CPOW reference to a function in the chrome process + // is callable from unprivileged content. Greasemonkey uses this + // functionality. + let func = msg.objects.func; + let sb = Cu.Sandbox('http://www.example.com', {}); + sb.func = func; + ok(sb.eval('func()') == 101, "can call parent's function in child"); + done_count++; if (done_count == 2) sendSyncMessage("cpows:done", {}); diff --git a/dom/base/test/chrome/cpows_parent.xul b/dom/base/test/chrome/cpows_parent.xul index 78ed01d80bb..bc69541219f 100644 --- a/dom/base/test/chrome/cpows_parent.xul +++ b/dom/base/test/chrome/cpows_parent.xul @@ -181,8 +181,11 @@ let func = message.objects.func; let result = func(n => 2*n); ok(result == 20, "result == 20"); + function f() { + return 101; + } let obj = {a:1, __exposedProps__: {"a": "r"}}; - savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj}); + savedMM.sendAsyncMessage("cpows:from_parent", {}, {obj: obj, func: f}); } // Make sure errors in this file actually hit window.onerror. diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index 0792b9a343a..0d8eee539c7 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -16,9 +16,11 @@ #include "xpcprivate.h" #include "XPCMaps.h" #include "mozilla/dom/BindingUtils.h" +#include "JavaScriptParent.h" #include "jsfriendapi.h" #include "mozilla/Likely.h" #include "nsContentUtils.h" +#include "nsXULAppAPI.h" using namespace JS; using namespace js; @@ -423,9 +425,12 @@ WrapperFactory::Rewrap(JSContext *cx, HandleObject existing, HandleObject obj, } // If this is a chrome function being exposed to content, we need to allow - // call (but nothing else). + // call (but nothing else). We allow CPOWs that purport to be function's + // here, but only in the content process. else if (originIsChrome && !targetIsChrome && - IdentifyStandardInstance(obj) == JSProto_Function) + (IdentifyStandardInstance(obj) == JSProto_Function || + (jsipc::IsCPOW(obj) && JS::IsCallable(obj) && + XRE_GetProcessType() == GeckoProcessType_Content))) { wrapper = &FilteringWrapper::singleton; } From 7260811c539268b1f1f3c1d686a10dbbecbeaa17 Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Tue, 4 Nov 2014 17:39:34 -0800 Subject: [PATCH 044/153] Bug 1091964 - [e10s] Cache isCallable and isConstructor for CPOWs (r=mrbkap) --- js/ipc/JavaScriptBase.h | 16 ------- js/ipc/JavaScriptTypes.ipdlh | 2 + js/ipc/PJavaScript.ipdl | 3 -- js/ipc/WrapperAnswer.cpp | 41 ----------------- js/ipc/WrapperAnswer.h | 3 -- js/ipc/WrapperOwner.cpp | 88 +++++++++++++++++------------------- js/ipc/WrapperOwner.h | 5 -- 7 files changed, 44 insertions(+), 114 deletions(-) diff --git a/js/ipc/JavaScriptBase.h b/js/ipc/JavaScriptBase.h index a80e784f3af..2ae230bb0f7 100644 --- a/js/ipc/JavaScriptBase.h +++ b/js/ipc/JavaScriptBase.h @@ -116,14 +116,6 @@ class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base return Answer::RecvDOMInstanceOf(ObjectId::deserialize(objId), prototypeID, depth, rs, instanceof); } - bool RecvIsCallable(const uint64_t &objId, bool *result) { - return Answer::RecvIsCallable(ObjectId::deserialize(objId), result); - } - - bool RecvIsConstructor(const uint64_t &objId, bool *result) { - return Answer::RecvIsConstructor(ObjectId::deserialize(objId), result); - } - bool RecvDropObject(const uint64_t &objId) { return Answer::RecvDropObject(ObjectId::deserialize(objId)); } @@ -215,14 +207,6 @@ class JavaScriptBase : public WrapperOwner, public WrapperAnswer, public Base return Base::SendDOMInstanceOf(objId.serialize(), prototypeID, depth, rs, instanceof); } - bool SendIsCallable(const ObjectId &objId, bool *result) { - return Base::SendIsCallable(objId.serialize(), result); - } - - bool SendIsConstructor(const ObjectId &objId, bool *result) { - return Base::SendIsConstructor(objId.serialize(), result); - } - /* The following code is needed to suppress a bogus MSVC warning (C4250). */ virtual bool toObjectVariant(JSContext *cx, JSObject *obj, ObjectVariant *objVarp) { diff --git a/js/ipc/JavaScriptTypes.ipdlh b/js/ipc/JavaScriptTypes.ipdlh index a5c57792b7b..975e3aa39e1 100644 --- a/js/ipc/JavaScriptTypes.ipdlh +++ b/js/ipc/JavaScriptTypes.ipdlh @@ -35,6 +35,8 @@ struct LocalObject struct RemoteObject { uint64_t serializedId; + bool isCallable; + bool isConstructor; }; union ObjectVariant diff --git a/js/ipc/PJavaScript.ipdl b/js/ipc/PJavaScript.ipdl index 6638ce19f3b..8f89fd139ca 100644 --- a/js/ipc/PJavaScript.ipdl +++ b/js/ipc/PJavaScript.ipdl @@ -46,9 +46,6 @@ both: prio(high) sync InstanceOf(uint64_t objId, JSIID iid) returns (ReturnStatus rs, bool instanceof); prio(high) sync DOMInstanceOf(uint64_t objId, int prototypeID, int depth) returns (ReturnStatus rs, bool instanceof); - prio(high) sync IsCallable(uint64_t objId) returns (bool result); - prio(high) sync IsConstructor(uint64_t objId) returns (bool result); - parent: async __delete__(); }; diff --git a/js/ipc/WrapperAnswer.cpp b/js/ipc/WrapperAnswer.cpp index e2ef50b309b..fa8a6a9f2f3 100644 --- a/js/ipc/WrapperAnswer.cpp +++ b/js/ipc/WrapperAnswer.cpp @@ -662,47 +662,6 @@ WrapperAnswer::RecvDOMInstanceOf(const ObjectId &objId, const int &prototypeID, return ok(rs); } -bool -WrapperAnswer::RecvIsCallable(const ObjectId &objId, bool *result) -{ - AutoSafeJSContext cx; - JSAutoRequest request(cx); - - RootedObject obj(cx, findObjectById(cx, objId)); - if (!obj) { - // This is very unfortunate, but we have no choice. - *result = false; - return true; - } - JSAutoCompartment ac(cx, obj); // Not really necessary here, but be safe. - - LOG("%s.isCallable()", ReceiverObj(objId)); - - *result = JS::IsCallable(obj); - return true; -} - -bool -WrapperAnswer::RecvIsConstructor(const ObjectId &objId, bool *result) -{ - AutoSafeJSContext cx; - JSAutoRequest request(cx); - - RootedObject obj(cx, findObjectById(cx, objId)); - if (!obj) { - // This is very unfortunate, but we have no choice. - *result = false; - return true; - } - JSAutoCompartment ac(cx, obj); // Not really necessary here, but be safe. - - LOG("%s.isConstructor()", ReceiverObj(objId)); - - *result = JS::IsConstructor(obj); - return true; -} - - bool WrapperAnswer::RecvDropObject(const ObjectId &objId) { diff --git a/js/ipc/WrapperAnswer.h b/js/ipc/WrapperAnswer.h index 180bad5abaa..772416abb2b 100644 --- a/js/ipc/WrapperAnswer.h +++ b/js/ipc/WrapperAnswer.h @@ -62,9 +62,6 @@ class WrapperAnswer : public virtual JavaScriptShared bool RecvDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth, ReturnStatus *rs, bool *instanceof); - bool RecvIsCallable(const ObjectId &objId, bool *result); - bool RecvIsConstructor(const ObjectId &objId, bool *result); - bool RecvDropObject(const ObjectId &objId); private: diff --git a/js/ipc/WrapperOwner.cpp b/js/ipc/WrapperOwner.cpp index 3bbb1414fd3..8eb6b30ae2a 100644 --- a/js/ipc/WrapperOwner.cpp +++ b/js/ipc/WrapperOwner.cpp @@ -18,12 +18,32 @@ using namespace JS; using namespace mozilla; using namespace mozilla::jsipc; +struct AuxCPOWData +{ + ObjectId id; + bool isCallable; + bool isConstructor; + + AuxCPOWData(ObjectId id, bool isCallable, bool isConstructor) + : id(id), + isCallable(isCallable), + isConstructor(isConstructor) + {} +}; + WrapperOwner::WrapperOwner(JSRuntime *rt) : JavaScriptShared(rt), inactive_(false) { } +static inline AuxCPOWData * +AuxCPOWDataOf(JSObject *obj) +{ + MOZ_ASSERT(IsCPOW(obj)); + return static_cast(GetProxyExtra(obj, 1).toPrivate()); +} + static inline WrapperOwner * OwnerOf(JSObject *obj) { @@ -36,13 +56,9 @@ WrapperOwner::idOfUnchecked(JSObject *obj) { MOZ_ASSERT(IsCPOW(obj)); - Value v = GetProxyExtra(obj, 1); - MOZ_ASSERT(v.isDouble()); - - ObjectId objId = ObjectId::deserialize(BitwiseCast(v.toDouble())); - MOZ_ASSERT(!objId.isNull()); - - return objId; + AuxCPOWData *aux = AuxCPOWDataOf(obj); + MOZ_ASSERT(!aux->id.isNull()); + return aux->id; } ObjectId @@ -689,6 +705,10 @@ void CPOWProxyHandler::finalize(JSFreeOp *fop, JSObject *proxy) const { OwnerOf(proxy)->drop(proxy); + + AuxCPOWData *aux = AuxCPOWDataOf(proxy); + if (aux) + delete aux; } void @@ -700,47 +720,15 @@ CPOWProxyHandler::objectMoved(JSObject *proxy, const JSObject *old) const bool CPOWProxyHandler::isCallable(JSObject *proxy) const { - WrapperOwner *parent = OwnerOf(proxy); - if (!parent->active()) - return false; - return parent->isCallable(proxy); -} - -bool -WrapperOwner::isCallable(JSObject *obj) -{ - ObjectId objId = idOf(obj); - - bool callable = false; - if (!SendIsCallable(objId, &callable)) { - NS_WARNING("IPC isCallable() failed"); - return false; - } - - return callable; + AuxCPOWData *aux = AuxCPOWDataOf(proxy); + return aux->isCallable; } bool CPOWProxyHandler::isConstructor(JSObject *proxy) const { - WrapperOwner *parent = OwnerOf(proxy); - if (!parent->active()) - return false; - return parent->isConstructor(proxy); -} - -bool -WrapperOwner::isConstructor(JSObject *obj) -{ - ObjectId objId = idOf(obj); - - bool constructor = false; - if (!SendIsConstructor(objId, &constructor)) { - NS_WARNING("IPC isConstructor() failed"); - return false; - } - - return constructor; + AuxCPOWData *aux = AuxCPOWDataOf(proxy); + return aux->isConstructor; } void @@ -895,6 +883,12 @@ WrapperOwner::ok(JSContext *cx, const ReturnStatus &status) return false; } +static RemoteObject +MakeRemoteObject(ObjectId id, JSObject *obj) +{ + return RemoteObject(id.serialize(), JS::IsCallable(obj), JS::IsConstructor(obj)); +} + bool WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *objVarp) { @@ -916,7 +910,7 @@ WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *ob ObjectId id = objectIdMap(waiveXray).find(obj); if (!id.isNull()) { MOZ_ASSERT(id.hasXrayWaiver() == waiveXray); - *objVarp = RemoteObject(id.serialize()); + *objVarp = MakeRemoteObject(id, obj); return true; } @@ -931,7 +925,7 @@ WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *ob if (!objectIdMap(waiveXray).add(cx, obj, id)) return false; - *objVarp = RemoteObject(id.serialize()); + *objVarp = MakeRemoteObject(id, obj); return true; } @@ -970,8 +964,10 @@ WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar) // Incref once we know the decref will be called. incref(); + AuxCPOWData *aux = new AuxCPOWData(objId, objVar.isCallable(), objVar.isConstructor()); + SetProxyExtra(obj, 0, PrivateValue(this)); - SetProxyExtra(obj, 1, DoubleValue(BitwiseCast(objId.serialize()))); + SetProxyExtra(obj, 1, PrivateValue(aux)); } if (!JS_WrapObject(cx, &obj)) diff --git a/js/ipc/WrapperOwner.h b/js/ipc/WrapperOwner.h index 47bad3ccfd4..5ff469dc233 100644 --- a/js/ipc/WrapperOwner.h +++ b/js/ipc/WrapperOwner.h @@ -61,8 +61,6 @@ class WrapperOwner : public virtual JavaScriptShared bool objectClassIs(JSContext *cx, JS::HandleObject obj, js::ESClassValue classValue); const char* className(JSContext *cx, JS::HandleObject proxy); bool regexp_toShared(JSContext *cx, JS::HandleObject proxy, js::RegExpGuard *g); - bool isCallable(JSObject *obj); - bool isConstructor(JSObject *obj); nsresult instanceOf(JSObject *obj, const nsID *id, bool *bp); @@ -151,9 +149,6 @@ class WrapperOwner : public virtual JavaScriptShared ReturnStatus *rs, bool *instanceof) = 0; virtual bool SendDOMInstanceOf(const ObjectId &objId, const int &prototypeID, const int &depth, ReturnStatus *rs, bool *instanceof) = 0; - - virtual bool SendIsCallable(const ObjectId &objId, bool *result) = 0; - virtual bool SendIsConstructor(const ObjectId &objId, bool *result) = 0; }; bool From d673d4c30d39a9152de997b98f04a874b6eff536 Mon Sep 17 00:00:00 2001 From: Bill McCloskey Date: Tue, 4 Nov 2014 17:40:08 -0800 Subject: [PATCH 045/153] Bug 1091970 - [e10s] Cache object tag in add-on shims for remote objects (r=mrbkap) --- browser/installer/package-manifest.in | 1 + js/ipc/JavaScriptTypes.ipdlh | 1 + js/ipc/WrapperOwner.cpp | 47 ++++++++++++++++--- js/ipc/WrapperOwner.h | 3 ++ js/xpconnect/idl/moz.build | 1 + js/xpconnect/idl/nsIRemoteTagService.idl | 13 +++++ js/xpconnect/idl/xpccomponents.idl | 11 ++++- js/xpconnect/src/XPCComponents.cpp | 10 ++++ .../addoncompat/addoncompat.manifest | 2 + toolkit/components/addoncompat/moz.build | 1 + .../addoncompat/multiprocessShims.js | 8 +--- .../addoncompat/remoteTagService.js | 39 +++++++++++++++ 12 files changed, 122 insertions(+), 15 deletions(-) create mode 100644 js/xpconnect/idl/nsIRemoteTagService.idl create mode 100644 toolkit/components/addoncompat/remoteTagService.js diff --git a/browser/installer/package-manifest.in b/browser/installer/package-manifest.in index 125354ff90a..7f81993c60e 100644 --- a/browser/installer/package-manifest.in +++ b/browser/installer/package-manifest.in @@ -441,6 +441,7 @@ @BINPATH@/components/nsUpdateTimerManager.js @BINPATH@/components/addoncompat.manifest @BINPATH@/components/multiprocessShims.js +@BINPATH@/components/remoteTagService.js @BINPATH@/components/pluginGlue.manifest @BINPATH@/components/ProcessSingleton.manifest @BINPATH@/components/MainProcessSingleton.js diff --git a/js/ipc/JavaScriptTypes.ipdlh b/js/ipc/JavaScriptTypes.ipdlh index 975e3aa39e1..2d19ccff82a 100644 --- a/js/ipc/JavaScriptTypes.ipdlh +++ b/js/ipc/JavaScriptTypes.ipdlh @@ -37,6 +37,7 @@ struct RemoteObject uint64_t serializedId; bool isCallable; bool isConstructor; + nsCString objectTag; }; union ObjectVariant diff --git a/js/ipc/WrapperOwner.cpp b/js/ipc/WrapperOwner.cpp index 8eb6b30ae2a..bb377fea64f 100644 --- a/js/ipc/WrapperOwner.cpp +++ b/js/ipc/WrapperOwner.cpp @@ -13,6 +13,8 @@ #include "xpcprivate.h" #include "WrapperFactory.h" +#include "nsIRemoteTagService.h" + using namespace js; using namespace JS; using namespace mozilla; @@ -24,10 +26,15 @@ struct AuxCPOWData bool isCallable; bool isConstructor; - AuxCPOWData(ObjectId id, bool isCallable, bool isConstructor) + // The object tag is just some auxilliary information that clients can use + // however they see fit. + nsCString objectTag; + + AuxCPOWData(ObjectId id, bool isCallable, bool isConstructor, const nsACString &objectTag) : id(id), isCallable(isCallable), - isConstructor(isConstructor) + isConstructor(isConstructor), + objectTag(objectTag) {} }; @@ -803,6 +810,17 @@ IsWrappedCPOW(JSObject *obj) return IsCPOW(unwrapped); } +void +GetWrappedCPOWTag(JSObject *obj, nsACString &out) +{ + JSObject *unwrapped = js::UncheckedUnwrap(obj, true); + MOZ_ASSERT(IsCPOW(unwrapped)); + + AuxCPOWData *aux = AuxCPOWDataOf(unwrapped); + if (aux) + out = aux->objectTag; +} + nsresult InstanceOf(JSObject *proxy, const nsID *id, bool *bp) { @@ -884,9 +902,21 @@ WrapperOwner::ok(JSContext *cx, const ReturnStatus &status) } static RemoteObject -MakeRemoteObject(ObjectId id, JSObject *obj) +MakeRemoteObject(JSContext *cx, ObjectId id, HandleObject obj) { - return RemoteObject(id.serialize(), JS::IsCallable(obj), JS::IsConstructor(obj)); + nsCString objectTag; + + nsCOMPtr service = + do_GetService("@mozilla.org/addons/remote-tag-service;1"); + if (service) { + RootedValue objVal(cx, ObjectValue(*obj)); + service->GetRemoteObjectTag(objVal, objectTag); + } + + return RemoteObject(id.serialize(), + JS::IsCallable(obj), + JS::IsConstructor(obj), + objectTag); } bool @@ -910,7 +940,7 @@ WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *ob ObjectId id = objectIdMap(waiveXray).find(obj); if (!id.isNull()) { MOZ_ASSERT(id.hasXrayWaiver() == waiveXray); - *objVarp = MakeRemoteObject(id, obj); + *objVarp = MakeRemoteObject(cx, id, obj); return true; } @@ -925,7 +955,7 @@ WrapperOwner::toObjectVariant(JSContext *cx, JSObject *objArg, ObjectVariant *ob if (!objectIdMap(waiveXray).add(cx, obj, id)) return false; - *objVarp = MakeRemoteObject(id, obj); + *objVarp = MakeRemoteObject(cx, id, obj); return true; } @@ -964,7 +994,10 @@ WrapperOwner::fromRemoteObjectVariant(JSContext *cx, RemoteObject objVar) // Incref once we know the decref will be called. incref(); - AuxCPOWData *aux = new AuxCPOWData(objId, objVar.isCallable(), objVar.isConstructor()); + AuxCPOWData *aux = new AuxCPOWData(objId, + objVar.isCallable(), + objVar.isConstructor(), + objVar.objectTag()); SetProxyExtra(obj, 0, PrivateValue(this)); SetProxyExtra(obj, 1, PrivateValue(aux)); diff --git a/js/ipc/WrapperOwner.h b/js/ipc/WrapperOwner.h index 5ff469dc233..8c51f218c93 100644 --- a/js/ipc/WrapperOwner.h +++ b/js/ipc/WrapperOwner.h @@ -163,6 +163,9 @@ InstanceOf(JSObject *obj, const nsID *id, bool *bp); bool DOMInstanceOf(JSContext *cx, JSObject *obj, int prototypeID, int depth, bool *bp); +void +GetWrappedCPOWTag(JSObject *obj, nsACString &out); + } // jsipc } // mozilla diff --git a/js/xpconnect/idl/moz.build b/js/xpconnect/idl/moz.build index 8dffa158e46..c1ed1e21c1b 100644 --- a/js/xpconnect/idl/moz.build +++ b/js/xpconnect/idl/moz.build @@ -8,6 +8,7 @@ XPIDL_SOURCES += [ 'mozIJSSubScriptLoader.idl', 'nsIAddonInterposition.idl', 'nsIJSRuntimeService.idl', + 'nsIRemoteTagService.idl', 'nsIScriptError.idl', 'nsIXPConnect.idl', 'nsIXPCScriptable.idl', diff --git a/js/xpconnect/idl/nsIRemoteTagService.idl b/js/xpconnect/idl/nsIRemoteTagService.idl new file mode 100644 index 00000000000..4602dfdb99f --- /dev/null +++ b/js/xpconnect/idl/nsIRemoteTagService.idl @@ -0,0 +1,13 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable,uuid(59dbe3d0-6084-11e4-9803-0800200c9a66)] +interface nsIRemoteTagService : nsISupports +{ + ACString getRemoteObjectTag(in jsval target); +}; diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl index fca4ffc9372..7cde41bf36a 100644 --- a/js/xpconnect/idl/xpccomponents.idl +++ b/js/xpconnect/idl/xpccomponents.idl @@ -122,7 +122,7 @@ interface ScheduledGCCallback : nsISupports /** * interface of Components.utils */ -[scriptable, uuid(ad41689c-ed0e-4a92-8f19-a53c2dba5711)] +[scriptable, uuid(2617a800-63c1-11e4-9803-0800200c9a66)] interface nsIXPCComponents_Utils : nsISupports { @@ -435,6 +435,15 @@ interface nsIXPCComponents_Utils : nsISupports */ bool isCrossProcessWrapper(in jsval obj); + /** + * CPOWs can have user data attached to them. This data originates + * in the local process via the + * nsIRemoteTagService.getRemoteObjectTag method. It's sent along + * with the CPOW to the remote process, where it can be fetched + * with this function, getCrossProcessWrapperTag. + */ + ACString getCrossProcessWrapperTag(in jsval obj); + /* * To be called from JS only. This is for Gecko internal use only, and may * disappear at any moment. diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp index 3b2c716f034..25a7f0bc8d6 100644 --- a/js/xpconnect/src/XPCComponents.cpp +++ b/js/xpconnect/src/XPCComponents.cpp @@ -3106,6 +3106,16 @@ nsXPCComponents_Utils::IsCrossProcessWrapper(HandleValue obj, bool *out) return NS_OK; } +NS_IMETHODIMP +nsXPCComponents_Utils::GetCrossProcessWrapperTag(HandleValue obj, nsACString &out) +{ + if (obj.isPrimitive() || !jsipc::IsWrappedCPOW(&obj.toObject())) + return NS_ERROR_INVALID_ARG; + + jsipc::GetWrappedCPOWTag(&obj.toObject(), out); + return NS_OK; +} + /* void recomputerWrappers(jsval vobj); */ NS_IMETHODIMP nsXPCComponents_Utils::RecomputeWrappers(HandleValue vobj, JSContext *cx) diff --git a/toolkit/components/addoncompat/addoncompat.manifest b/toolkit/components/addoncompat/addoncompat.manifest index 962562bfcb6..4273bf246c3 100644 --- a/toolkit/components/addoncompat/addoncompat.manifest +++ b/toolkit/components/addoncompat/addoncompat.manifest @@ -1,2 +1,4 @@ component {1363d5f0-d95e-11e3-9c1a-0800200c9a66} multiprocessShims.js contract @mozilla.org/addons/multiprocess-shims;1 {1363d5f0-d95e-11e3-9c1a-0800200c9a66} +component {dfd07380-6083-11e4-9803-0800200c9a66} remoteTagService.js +contract @mozilla.org/addons/remote-tag-service;1 {dfd07380-6083-11e4-9803-0800200c9a66} diff --git a/toolkit/components/addoncompat/moz.build b/toolkit/components/addoncompat/moz.build index f915e7370e4..56e8d050a72 100644 --- a/toolkit/components/addoncompat/moz.build +++ b/toolkit/components/addoncompat/moz.build @@ -9,6 +9,7 @@ TEST_DIRS += ['tests'] EXTRA_COMPONENTS += [ 'addoncompat.manifest', 'multiprocessShims.js', + 'remoteTagService.js', ] EXTRA_JS_MODULES += [ diff --git a/toolkit/components/addoncompat/multiprocessShims.js b/toolkit/components/addoncompat/multiprocessShims.js index 637685571be..9cbcce8afc0 100644 --- a/toolkit/components/addoncompat/multiprocessShims.js +++ b/toolkit/components/addoncompat/multiprocessShims.js @@ -77,13 +77,7 @@ AddonInterpositionService.prototype = { // determines the type of the target object. getObjectTag: function(target) { if (Cu.isCrossProcessWrapper(target)) { - if (target instanceof Ci.nsIDocShellTreeItem) { - return "ContentDocShellTreeItem"; - } - - if (target instanceof Ci.nsIDOMDocument) { - return "ContentDocument"; - } + return Cu.getCrossProcessWrapperTag(target); } const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; diff --git a/toolkit/components/addoncompat/remoteTagService.js b/toolkit/components/addoncompat/remoteTagService.js new file mode 100644 index 00000000000..9c1a443baa1 --- /dev/null +++ b/toolkit/components/addoncompat/remoteTagService.js @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +const Cu = Components.utils; +const Ci = Components.interfaces; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +function RemoteTagServiceService() +{ +} + +RemoteTagServiceService.prototype = { + classID: Components.ID("{dfd07380-6083-11e4-9803-0800200c9a66}"), + QueryInterface: XPCOMUtils.generateQI([Ci.nsIRemoteTagService, Ci.nsISupportsWeakReference]), + + /** + * CPOWs can have user data attached to them. This data originates + * in the local process from this function, getRemoteObjectTag. It's + * sent along with the CPOW to the remote process, where it can be + * fetched with Components.utils.getCrossProcessWrapperTag. + */ + getRemoteObjectTag: function(target) { + if (target instanceof Ci.nsIDocShellTreeItem) { + return "ContentDocShellTreeItem"; + } + + if (target instanceof Ci.nsIDOMDocument) { + return "ContentDocument"; + } + + return "generic"; + } +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([RemoteTagServiceService]); From d08dfee91d92bbed7f1fcaa3b6dceb15fefc052e Mon Sep 17 00:00:00 2001 From: Brian Hackett Date: Tue, 4 Nov 2014 18:44:03 -0700 Subject: [PATCH 046/153] Bug 1089665 - Fixup dictionary objects earlier after swapping them, r=billm. --- js/src/jsobj.cpp | 22 +++++++++++++++------- js/src/jsobj.h | 4 ++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index 6749db2d6d8..5264c306631 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2387,6 +2387,15 @@ NativeObject::fillInAfterSwap(JSContext *cx, const Vector &values, void * initSlotRange(0, values.begin(), values.length()); } +void +JSObject::fixDictionaryShapeAfterSwap() +{ + // Dictionary shapes can point back to their containing objects, so after + // swapping the guts of those objects fix the pointers up. + if (isNative() && as().inDictionaryMode()) + shape_->listp = &shape_; +} + /* Use this method with extreme caution. It trades the guts of two objects. */ bool JSObject::swap(JSContext *cx, HandleObject a, HandleObject b) @@ -2441,6 +2450,9 @@ JSObject::swap(JSContext *cx, HandleObject a, HandleObject b) js_memcpy(tmp, a, size); js_memcpy(a, b, size); js_memcpy(b, tmp, size); + + a->fixDictionaryShapeAfterSwap(); + b->fixDictionaryShapeAfterSwap(); } else { // Avoid GC in here to avoid confusing the tracing code with our // intermediate state. @@ -2478,19 +2490,15 @@ JSObject::swap(JSContext *cx, HandleObject a, HandleObject b) js_memcpy(a, b, sizeof tmp); js_memcpy(b, &tmp, sizeof tmp); + a->fixDictionaryShapeAfterSwap(); + b->fixDictionaryShapeAfterSwap(); + if (na) b->as().fillInAfterSwap(cx, avals, apriv); if (nb) a->as().fillInAfterSwap(cx, bvals, bpriv); } - // Dictionary shapes can point back to their containing objects, so fix - // those pointers up. - if (a->isNative() && a->as().inDictionaryMode()) - a->lastProperty()->listp = &a->shape_; - if (b->isNative() && b->as().inDictionaryMode()) - b->lastProperty()->listp = &b->shape_; - // Swapping the contents of two objects invalidates type sets which contain // either of the objects, so mark all such sets as unknown. MarkTypeObjectUnknownProperties(cx, a->type(), !a->hasSingletonType()); diff --git a/js/src/jsobj.h b/js/src/jsobj.h index eacceebb516..c02d857f903 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -705,6 +705,10 @@ class JSObject : public js::gc::Cell static bool swap(JSContext *cx, JS::HandleObject a, JS::HandleObject b); + private: + void fixDictionaryShapeAfterSwap(); + + public: inline void initArrayClass(); /* From 16d46a1e8fa543ca11f8c4afb0c0c70a2880b3a6 Mon Sep 17 00:00:00 2001 From: Chris Pearce Date: Wed, 5 Nov 2014 15:11:44 +1300 Subject: [PATCH 047/153] Bug 1090523 - Add more logging to help debug infrequent EME/MSE failure. r=edwin --- dom/media/test/eme.js | 64 +++++++++++++------ dom/media/test/test_eme_canvas_blocked.html | 4 +- dom/media/test/test_eme_playback.html | 24 +++---- .../test/test_eme_stream_capture_blocked.html | 14 ++-- 4 files changed, 65 insertions(+), 41 deletions(-) diff --git a/dom/media/test/eme.js b/dom/media/test/eme.js index f01455b9f35..c166b78c31c 100644 --- a/dom/media/test/eme.js +++ b/dom/media/test/eme.js @@ -50,14 +50,34 @@ function HexToBase64(hex) return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_"); } +function TimeStamp(token) { + function pad(x) { + return (x < 10) ? "0" + x : x; + } + var now = new Date(); + var ms = now.getMilliseconds(); + var time = "[" + + pad(now.getHours()) + ":" + + pad(now.getMinutes()) + ":" + + pad(now.getSeconds()) + "." + + ms + + "]" + + (ms < 10 ? " " : (ms < 100 ? " " : "")); + return token ? (time + " " + token) : time; +} + +function Log(token, msg) { + info(TimeStamp(token) + " " + msg); +} + function UpdateSessionFunc(test, token) { return function(ev) { var msgStr = ArrayBufferToString(ev.message); var msg = JSON.parse(msgStr); - info(token + " got message from CDM: " + msgStr); - is(msg.type, test.sessionType, token + " key session type should match"); - ok(msg.kids, token + " message event should contain key ID array"); + Log(token, "got message from CDM: " + msgStr); + is(msg.type, test.sessionType, TimeStamp(token) + " key session type should match"); + ok(msg.kids, TimeStamp(token) + " message event should contain key ID array"); var outKeys = []; @@ -67,7 +87,7 @@ function UpdateSessionFunc(test, token) { var key = test.keys[idHex]; if (key) { - info(token + " found key " + key + " for key id " + idHex); + Log(token, "found key " + key + " for key id " + idHex); outKeys.push({ "kty":"oct", "alg":"A128KW", @@ -75,7 +95,7 @@ function UpdateSessionFunc(test, token) { "k":HexToBase64(key) }); } else { - bail(token + " Couldn't find key for key id " + idHex); + bail(token + " couldn't find key for key id " + idHex); } } @@ -83,15 +103,15 @@ function UpdateSessionFunc(test, token) { "keys" : outKeys, "type" : msg.type }); - info(token + " sending update message to CDM: " + update); + Log(token, "sending update message to CDM: " + update); ev.target.update(StringToArrayBuffer(update)).then(function() { - info(token + " MediaKeySession update ok!"); + Log(token, "MediaKeySession update ok!"); }, bail(token + " MediaKeySession update failed")); } } -function PlayFragmented(test, elem) +function PlayFragmented(test, elem, token) { return new Promise(function(resolve, reject) { var ms = new MediaSource(); @@ -102,6 +122,7 @@ function PlayFragmented(test, elem) function addNextFragment() { if (curFragment >= test.fragments.length) { + Log(token, "addNextFragment() end of stream"); ms.endOfStream(); resolve(); return; @@ -114,18 +135,19 @@ function PlayFragmented(test, elem) req.responseType = "arraybuffer"; req.addEventListener("load", function() { - info("fetch of " + fragmentFile + " complete"); + Log(token, "fetch of " + fragmentFile + " complete, appending"); sb.appendBuffer(new Uint8Array(req.response)); }); - req.addEventListener("error", bail("Error fetching " + fragmentFile)); - req.addEventListener("abort", bail("Aborted fetching " + fragmentFile)); + req.addEventListener("error", bail(token + " error fetching " + fragmentFile)); + req.addEventListener("abort", bail(token + " aborted fetching " + fragmentFile)); - info("fetching resource " + fragmentFile); + Log(token, "addNextFragment() fetching next fragment " + fragmentFile); req.send(null); } ms.addEventListener("sourceopen", function () { + Log(token, "sourceopen"); sb = ms.addSourceBuffer(test.type); sb.addEventListener("updateend", addNextFragment); @@ -137,10 +159,10 @@ function PlayFragmented(test, elem) // Returns a promise that is resovled when the media element is ready to have // its play() function called; when it's loaded MSE fragments, or once the load // has started for non-MSE video. -function LoadTest(test, elem) +function LoadTest(test, elem, token) { if (test.fragments) { - return PlayFragmented(test, elem); + return PlayFragmented(test, elem, token); } // This file isn't fragmented; set the media source normally. @@ -155,10 +177,12 @@ function SetupEME(test, token, params) var v = document.createElement("video"); // Log events dispatched to make debugging easier... - ["loadstart", "loadedmetadata", "loadeddata", "ended", - "play", "canplay", "playing", "canplaythrough"].forEach(function (e) { + [ "canplay", "canplaythrough", "ended", "error", "loadeddata", + "loadedmetadata", "loadstart", "pause", "play", "playing", "progress", + "stalled", "suspend", "waiting", + ].forEach(function (e) { v.addEventListener(e, function(event) { - info(token + " " + e); + Log(token, "" + e); }, false); }); @@ -167,13 +191,13 @@ function SetupEME(test, token, params) : bail(token + " Failed to set MediaKeys on