From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- js/xpconnect/crashtests/117307-1.html | 20 + js/xpconnect/crashtests/193710.html | 11 + js/xpconnect/crashtests/290162-1.html | 5 + js/xpconnect/crashtests/326615-1.html | 16 + js/xpconnect/crashtests/328553-1.html | 13 + js/xpconnect/crashtests/346258-1.html | 12 + js/xpconnect/crashtests/346512-1-frame1.xhtml | 16 + js/xpconnect/crashtests/346512-1-frame2.xhtml | 15 + js/xpconnect/crashtests/346512-1.xhtml | 30 + js/xpconnect/crashtests/382133-1.html | 3 + js/xpconnect/crashtests/386680-1.html | 22 + js/xpconnect/crashtests/394810-1.html | 4 + js/xpconnect/crashtests/400349-1.html | 20 + js/xpconnect/crashtests/403356-1.html | 13 + js/xpconnect/crashtests/418139-1.svg | 22 + js/xpconnect/crashtests/420513-1.html | 11 + js/xpconnect/crashtests/453935-1.html | 37 + js/xpconnect/crashtests/462926.html | 12 + js/xpconnect/crashtests/467693-1.html | 16 + js/xpconnect/crashtests/468552-1.html | 18 + js/xpconnect/crashtests/471366-1.html | 12 + js/xpconnect/crashtests/475185-1.html | 13 + js/xpconnect/crashtests/475291-1.html | 14 + js/xpconnect/crashtests/503286-1.html | 23 + js/xpconnect/crashtests/504000-1.html | 21 + js/xpconnect/crashtests/509075-1.html | 28 + js/xpconnect/crashtests/512815-1.html | 21 + js/xpconnect/crashtests/515726-1.html | 26 + js/xpconnect/crashtests/545291-1.html | 12 + js/xpconnect/crashtests/558979.html | 13 + js/xpconnect/crashtests/582649.html | 12 + js/xpconnect/crashtests/601284-1.html | 22 + js/xpconnect/crashtests/603146-1.html | 7 + js/xpconnect/crashtests/603858-1.html | 8 + js/xpconnect/crashtests/608963.html | 5 + js/xpconnect/crashtests/616930-1.html | 15 + js/xpconnect/crashtests/639737-1.html | 19 + js/xpconnect/crashtests/648206-1.html | 7 + js/xpconnect/crashtests/705875.html | 7 + js/xpconnect/crashtests/720305-1.html | 8 + js/xpconnect/crashtests/723465.html | 19 + js/xpconnect/crashtests/732870.html | 23 + js/xpconnect/crashtests/751995.html | 36 + js/xpconnect/crashtests/752038-iframe.html | 11 + js/xpconnect/crashtests/752038.html | 28 + js/xpconnect/crashtests/753162.html | 23 + js/xpconnect/crashtests/754311-iframe.html | 21 + js/xpconnect/crashtests/754311.html | 16 + js/xpconnect/crashtests/761831.html | 23 + js/xpconnect/crashtests/786142-iframe.html | 84 + js/xpconnect/crashtests/786142.html | 16 + js/xpconnect/crashtests/797583.html | 6 + js/xpconnect/crashtests/806751.html | 26 + js/xpconnect/crashtests/833856.html | 14 + js/xpconnect/crashtests/851418.html | 23 + js/xpconnect/crashtests/854139.html | 10 + js/xpconnect/crashtests/854604.html | 10 + js/xpconnect/crashtests/898939.html | 18 + js/xpconnect/crashtests/905523.html | 24904 +++++++++++++++++++ js/xpconnect/crashtests/938297.html | 24 + js/xpconnect/crashtests/crashtests.list | 56 + js/xpconnect/idl/moz.build | 22 + js/xpconnect/idl/mozIJSSubScriptLoader.idl | 67 + js/xpconnect/idl/nsIAddonInterposition.idl | 67 + js/xpconnect/idl/nsIScriptError.idl | 122 + js/xpconnect/idl/nsIXPCScriptable.idl | 157 + js/xpconnect/idl/nsIXPConnect.idl | 542 + js/xpconnect/idl/xpcIJSGetFactory.idl | 18 + js/xpconnect/idl/xpcIJSModuleLoader.idl | 82 + js/xpconnect/idl/xpcIJSWeakReference.idl | 17 + js/xpconnect/idl/xpccomponents.idl | 732 + js/xpconnect/idl/xpcexception.idl | 30 + js/xpconnect/idl/xpcjsid.idl | 47 + js/xpconnect/loader/ISO8601DateUtils.jsm | 144 + js/xpconnect/loader/XPCOMUtils.jsm | 471 + js/xpconnect/loader/moz.build | 28 + js/xpconnect/loader/mozJSComponentLoader.cpp | 1437 ++ js/xpconnect/loader/mozJSComponentLoader.h | 161 + js/xpconnect/loader/mozJSLoaderUtils.cpp | 85 + js/xpconnect/loader/mozJSLoaderUtils.h | 37 + js/xpconnect/loader/mozJSSubScriptLoader.cpp | 929 + js/xpconnect/loader/mozJSSubScriptLoader.h | 53 + js/xpconnect/moz.build | 16 + js/xpconnect/public/moz.build | 13 + js/xpconnect/public/nsAXPCNativeCallContext.h | 32 + js/xpconnect/public/nsTArrayHelpers.h | 92 + js/xpconnect/public/xpc_make_class.h | 175 + js/xpconnect/public/xpc_map_end.h | 204 + js/xpconnect/shell/Makefile.in | 6 + js/xpconnect/shell/moz.build | 60 + js/xpconnect/shell/xpcshell.cpp | 69 + js/xpconnect/shell/xpcshell.exe.manifest | 31 + js/xpconnect/shell/xpcshell.rc | 6 + js/xpconnect/shell/xpcshellMacUtils.h | 9 + js/xpconnect/shell/xpcshellMacUtils.mm | 19 + js/xpconnect/src/BackstagePass.h | 58 + js/xpconnect/src/ExportHelpers.cpp | 491 + js/xpconnect/src/README | 3 + js/xpconnect/src/Sandbox.cpp | 1966 ++ js/xpconnect/src/SandboxPrivate.h | 67 + js/xpconnect/src/XPCCallContext.cpp | 276 + js/xpconnect/src/XPCComponents.cpp | 3563 +++ js/xpconnect/src/XPCConvert.cpp | 1799 ++ js/xpconnect/src/XPCDebug.cpp | 63 + js/xpconnect/src/XPCException.cpp | 80 + js/xpconnect/src/XPCForwards.h | 64 + js/xpconnect/src/XPCInlines.h | 545 + js/xpconnect/src/XPCJSContext.cpp | 3771 +++ js/xpconnect/src/XPCJSID.cpp | 816 + js/xpconnect/src/XPCJSMemoryReporter.h | 33 + js/xpconnect/src/XPCJSWeakReference.cpp | 94 + js/xpconnect/src/XPCJSWeakReference.h | 29 + js/xpconnect/src/XPCLocale.cpp | 289 + js/xpconnect/src/XPCLog.cpp | 94 + js/xpconnect/src/XPCLog.h | 64 + js/xpconnect/src/XPCMaps.cpp | 405 + js/xpconnect/src/XPCMaps.h | 606 + js/xpconnect/src/XPCModule.cpp | 24 + js/xpconnect/src/XPCModule.h | 57 + js/xpconnect/src/XPCRuntimeService.cpp | 189 + js/xpconnect/src/XPCShellImpl.cpp | 1765 ++ js/xpconnect/src/XPCString.cpp | 143 + js/xpconnect/src/XPCThrower.cpp | 179 + js/xpconnect/src/XPCVariant.cpp | 800 + js/xpconnect/src/XPCWrappedJS.cpp | 731 + js/xpconnect/src/XPCWrappedJSClass.cpp | 1457 ++ js/xpconnect/src/XPCWrappedNative.cpp | 2325 ++ js/xpconnect/src/XPCWrappedNativeInfo.cpp | 800 + js/xpconnect/src/XPCWrappedNativeJSOps.cpp | 1331 + js/xpconnect/src/XPCWrappedNativeProto.cpp | 208 + js/xpconnect/src/XPCWrappedNativeScope.cpp | 934 + js/xpconnect/src/XPCWrapper.cpp | 97 + js/xpconnect/src/XPCWrapper.h | 40 + js/xpconnect/src/jsshell.msg | 12 + js/xpconnect/src/moz.build | 70 + js/xpconnect/src/nsScriptError.cpp | 345 + js/xpconnect/src/nsScriptErrorWithStack.cpp | 119 + js/xpconnect/src/nsXPConnect.cpp | 1336 + js/xpconnect/src/qsObjectHelper.h | 53 + js/xpconnect/src/xpc.msg | 228 + js/xpconnect/src/xpcObjectHelper.h | 137 + js/xpconnect/src/xpcprivate.h | 3426 +++ js/xpconnect/src/xpcpublic.h | 635 + js/xpconnect/tests/browser/browser.ini | 4 + .../tests/browser/browser_deadObjectOnUnload.html | 18 + js/xpconnect/tests/browser/browser_dead_object.js | 21 + js/xpconnect/tests/browser/moz.build | 8 + js/xpconnect/tests/chrome/bug503926.xul | 30 + js/xpconnect/tests/chrome/chrome.ini | 119 + js/xpconnect/tests/chrome/file_bug1050049.xml | 10 + js/xpconnect/tests/chrome/file_bug1281071.html | 13 + js/xpconnect/tests/chrome/file_bug618176.xul | 44 + js/xpconnect/tests/chrome/file_bug996069.html | 11 + .../tests/chrome/file_discardSystemSource.html | 18 + js/xpconnect/tests/chrome/file_evalInSandbox.html | 1 + js/xpconnect/tests/chrome/file_expandosharing.jsm | 10 + js/xpconnect/tests/chrome/moz.build | 12 + js/xpconnect/tests/chrome/outoflinexulscript.js | 6 + js/xpconnect/tests/chrome/subscript.js | 6 + js/xpconnect/tests/chrome/test_APIExposer.xul | 51 + js/xpconnect/tests/chrome/test_bug1041626.xul | 64 + js/xpconnect/tests/chrome/test_bug1042436.xul | 49 + js/xpconnect/tests/chrome/test_bug1050049.html | 56 + js/xpconnect/tests/chrome/test_bug1065185.html | 64 + js/xpconnect/tests/chrome/test_bug1074863.html | 32 + js/xpconnect/tests/chrome/test_bug1092477.xul | 36 + js/xpconnect/tests/chrome/test_bug1124898.html | 47 + js/xpconnect/tests/chrome/test_bug1126911.html | 41 + js/xpconnect/tests/chrome/test_bug1281071.xul | 33 + js/xpconnect/tests/chrome/test_bug361111.xul | 33 + js/xpconnect/tests/chrome/test_bug448587.xul | 37 + js/xpconnect/tests/chrome/test_bug484459.xul | 38 + js/xpconnect/tests/chrome/test_bug500931.xul | 41 + js/xpconnect/tests/chrome/test_bug503926.xul | 59 + js/xpconnect/tests/chrome/test_bug533596.xul | 56 + js/xpconnect/tests/chrome/test_bug571849.xul | 44 + js/xpconnect/tests/chrome/test_bug596580.xul | 48 + js/xpconnect/tests/chrome/test_bug601803.xul | 38 + js/xpconnect/tests/chrome/test_bug610390.xul | 33 + js/xpconnect/tests/chrome/test_bug614757.xul | 34 + js/xpconnect/tests/chrome/test_bug616992.xul | 32 + js/xpconnect/tests/chrome/test_bug618176.xul | 31 + js/xpconnect/tests/chrome/test_bug654370.xul | 29 + js/xpconnect/tests/chrome/test_bug658560.xul | 39 + js/xpconnect/tests/chrome/test_bug658909.xul | 92 + js/xpconnect/tests/chrome/test_bug664689.xul | 30 + js/xpconnect/tests/chrome/test_bug679861.xul | 39 + js/xpconnect/tests/chrome/test_bug706301.xul | 52 + js/xpconnect/tests/chrome/test_bug720619.xul | 49 + js/xpconnect/tests/chrome/test_bug726949.xul | 41 + js/xpconnect/tests/chrome/test_bug732665.xul | 81 + js/xpconnect/tests/chrome/test_bug732665_meta.js | 26 + js/xpconnect/tests/chrome/test_bug738244.xul | 59 + js/xpconnect/tests/chrome/test_bug743843.xul | 39 + js/xpconnect/tests/chrome/test_bug760076.xul | 49 + js/xpconnect/tests/chrome/test_bug760131.html | 44 + js/xpconnect/tests/chrome/test_bug763343.xul | 39 + js/xpconnect/tests/chrome/test_bug771429.xul | 51 + js/xpconnect/tests/chrome/test_bug773962.xul | 82 + js/xpconnect/tests/chrome/test_bug792280.xul | 45 + js/xpconnect/tests/chrome/test_bug793433.xul | 48 + js/xpconnect/tests/chrome/test_bug795275.xul | 88 + js/xpconnect/tests/chrome/test_bug799348.xul | 50 + js/xpconnect/tests/chrome/test_bug801241.xul | 46 + js/xpconnect/tests/chrome/test_bug812415.xul | 91 + js/xpconnect/tests/chrome/test_bug853283.xul | 41 + js/xpconnect/tests/chrome/test_bug853571.xul | 64 + js/xpconnect/tests/chrome/test_bug858101.xul | 55 + js/xpconnect/tests/chrome/test_bug860494.xul | 57 + js/xpconnect/tests/chrome/test_bug865948.xul | 36 + js/xpconnect/tests/chrome/test_bug866823.xul | 50 + js/xpconnect/tests/chrome/test_bug895340.xul | 64 + js/xpconnect/tests/chrome/test_bug932906.xul | 72 + js/xpconnect/tests/chrome/test_bug996069.xul | 53 + js/xpconnect/tests/chrome/test_chrometoSource.xul | 62 + js/xpconnect/tests/chrome/test_cloneInto.xul | 198 + js/xpconnect/tests/chrome/test_cows.xul | 266 + .../tests/chrome/test_discardSystemSource.xul | 81 + js/xpconnect/tests/chrome/test_documentdomain.xul | 101 + .../chrome/test_doublewrappedcompartments.xul | 42 + js/xpconnect/tests/chrome/test_evalInSandbox.xul | 202 + js/xpconnect/tests/chrome/test_evalInWindow.xul | 73 + js/xpconnect/tests/chrome/test_exnstack.xul | 68 + js/xpconnect/tests/chrome/test_expandosharing.xul | 145 + js/xpconnect/tests/chrome/test_exposeInDerived.xul | 46 + js/xpconnect/tests/chrome/test_getweakmapkeys.xul | 67 + .../tests/chrome/test_localstorage_with_nsEp.xul | 38 + js/xpconnect/tests/chrome/test_matches.xul | 50 + js/xpconnect/tests/chrome/test_nodelists.xul | 52 + .../tests/chrome/test_nsScriptErrorWithStack.html | 63 + .../tests/chrome/test_onGarbageCollection.html | 35 + .../tests/chrome/test_paris_weakmap_keys.xul | 80 + js/xpconnect/tests/chrome/test_precisegc.xul | 27 + js/xpconnect/tests/chrome/test_sandboxImport.xul | 39 + js/xpconnect/tests/chrome/test_scriptSettings.xul | 127 + js/xpconnect/tests/chrome/test_watchpoints.xul | 75 + .../tests/chrome/test_weakmap_keys_preserved.xul | 37 + .../tests/chrome/test_weakmap_keys_preserved2.xul | 84 + js/xpconnect/tests/chrome/test_weakmaps.xul | 272 + js/xpconnect/tests/chrome/test_weakref.xul | 34 + .../tests/chrome/test_windowProxyDeadWrapper.html | 72 + js/xpconnect/tests/chrome/test_wrappers-2.xul | 215 + js/xpconnect/tests/chrome/test_wrappers.xul | 96 + js/xpconnect/tests/chrome/test_xrayToJS.xul | 948 + js/xpconnect/tests/chrome/utf8_subscript.js | 3 + .../tests/chrome/worker_discardSystemSource.js | 5 + js/xpconnect/tests/components/js/xpctest.manifest | 28 + .../tests/components/js/xpctest_attributes.js | 48 + .../tests/components/js/xpctest_bug809674.js | 19 + .../tests/components/js/xpctest_interfaces.js | 48 + js/xpconnect/tests/components/js/xpctest_params.js | 85 + .../components/js/xpctest_returncode_child.js | 49 + js/xpconnect/tests/components/js/xpctest_utils.js | 17 + js/xpconnect/tests/components/native/moz.build | 19 + .../tests/components/native/xpctest.manifest | 1 + .../tests/components/native/xpctest_attributes.cpp | 139 + .../tests/components/native/xpctest_module.cpp | 59 + .../tests/components/native/xpctest_params.cpp | 308 + .../tests/components/native/xpctest_private.h | 80 + .../tests/components/native/xpctest_returncode.cpp | 27 + js/xpconnect/tests/idl/moz.build | 25 + js/xpconnect/tests/idl/xpctest_attributes.idl | 33 + js/xpconnect/tests/idl/xpctest_bug809674.idl | 18 + js/xpconnect/tests/idl/xpctest_interfaces.idl | 27 + js/xpconnect/tests/idl/xpctest_params.idl | 90 + js/xpconnect/tests/idl/xpctest_returncode.idl | 45 + js/xpconnect/tests/idl/xpctest_utils.idl | 19 + js/xpconnect/tests/mochitest/bug500931_helper.html | 8 + js/xpconnect/tests/mochitest/bug504877_helper.html | 10 + js/xpconnect/tests/mochitest/bug571849_helper.html | 7 + js/xpconnect/tests/mochitest/bug589028_helper.html | 27 + js/xpconnect/tests/mochitest/bug92773_helper.html | 7 + .../tests/mochitest/chrome_wrappers_helper.html | 30 + js/xpconnect/tests/mochitest/file1_bug629227.html | 32 + js/xpconnect/tests/mochitest/file2_bug629227.html | 11 + js/xpconnect/tests/mochitest/file_bug505915.html | 10 + js/xpconnect/tests/mochitest/file_bug650273.html | 31 + js/xpconnect/tests/mochitest/file_bug658560.html | 4 + js/xpconnect/tests/mochitest/file_bug706301.html | 27 + js/xpconnect/tests/mochitest/file_bug720619.html | 10 + js/xpconnect/tests/mochitest/file_bug738244.html | 10 + js/xpconnect/tests/mochitest/file_bug760131.html | 23 + js/xpconnect/tests/mochitest/file_bug781476.html | 15 + js/xpconnect/tests/mochitest/file_bug789713.html | 44 + js/xpconnect/tests/mochitest/file_bug795275.html | 28 + js/xpconnect/tests/mochitest/file_bug795275.xml | 19 + js/xpconnect/tests/mochitest/file_bug799348.html | 11 + js/xpconnect/tests/mochitest/file_bug802557.html | 62 + js/xpconnect/tests/mochitest/file_bug860494.html | 16 + .../tests/mochitest/file_crossOriginObjects.html | 35 + .../file_crossOriginObjects_documentDomain.html | 55 + .../mochitest/file_crosscompartment_weakmap.html | 9 + .../tests/mochitest/file_documentdomain.html | 41 + .../mochitest/file_doublewrappedcompartments.html | 10 + js/xpconnect/tests/mochitest/file_empty.html | 3 + .../tests/mochitest/file_evalInSandbox.html | 8 + js/xpconnect/tests/mochitest/file_exnstack.html | 23 + .../tests/mochitest/file_expandosharing.html | 34 + js/xpconnect/tests/mochitest/file_matches.html | 1 + js/xpconnect/tests/mochitest/file_nodelists.html | 7 + js/xpconnect/tests/mochitest/file_wrappers-2.html | 13 + js/xpconnect/tests/mochitest/inner.html | 7 + js/xpconnect/tests/mochitest/mochitest.ini | 107 + js/xpconnect/tests/mochitest/moz.build | 8 + js/xpconnect/tests/mochitest/test1_bug629331.html | 19 + js/xpconnect/tests/mochitest/test2_bug629331.html | 18 + js/xpconnect/tests/mochitest/test_bug1005806.html | 27 + js/xpconnect/tests/mochitest/test_bug1094930.html | 29 + js/xpconnect/tests/mochitest/test_bug1158558.html | 47 + js/xpconnect/tests/mochitest/test_bug384632.html | 34 + js/xpconnect/tests/mochitest/test_bug390488.html | 64 + js/xpconnect/tests/mochitest/test_bug393269.html | 46 + js/xpconnect/tests/mochitest/test_bug396851.html | 41 + js/xpconnect/tests/mochitest/test_bug428021.html | 40 + js/xpconnect/tests/mochitest/test_bug446584.html | 47 + js/xpconnect/tests/mochitest/test_bug462428.html | 51 + js/xpconnect/tests/mochitest/test_bug478438.html | 65 + js/xpconnect/tests/mochitest/test_bug484107.html | 99 + js/xpconnect/tests/mochitest/test_bug500691.html | 27 + js/xpconnect/tests/mochitest/test_bug504877.html | 64 + js/xpconnect/tests/mochitest/test_bug505915.html | 50 + js/xpconnect/tests/mochitest/test_bug560351.html | 36 + js/xpconnect/tests/mochitest/test_bug585745.html | 43 + js/xpconnect/tests/mochitest/test_bug589028.html | 62 + js/xpconnect/tests/mochitest/test_bug601299.html | 18 + js/xpconnect/tests/mochitest/test_bug605167.html | 56 + js/xpconnect/tests/mochitest/test_bug618017.html | 28 + js/xpconnect/tests/mochitest/test_bug623437.html | 43 + js/xpconnect/tests/mochitest/test_bug628410.html | 31 + js/xpconnect/tests/mochitest/test_bug628794.html | 43 + js/xpconnect/tests/mochitest/test_bug629227.html | 47 + js/xpconnect/tests/mochitest/test_bug629331.html | 37 + js/xpconnect/tests/mochitest/test_bug636097.html | 62 + js/xpconnect/tests/mochitest/test_bug650273.html | 42 + js/xpconnect/tests/mochitest/test_bug655297-1.html | 49 + js/xpconnect/tests/mochitest/test_bug655297-2.html | 49 + js/xpconnect/tests/mochitest/test_bug661980.html | 61 + js/xpconnect/tests/mochitest/test_bug691059.html | 59 + js/xpconnect/tests/mochitest/test_bug720619.html | 55 + js/xpconnect/tests/mochitest/test_bug731471.html | 42 + js/xpconnect/tests/mochitest/test_bug764389.html | 40 + js/xpconnect/tests/mochitest/test_bug772288.html | 50 + js/xpconnect/tests/mochitest/test_bug781476.html | 36 + js/xpconnect/tests/mochitest/test_bug789713.html | 39 + js/xpconnect/tests/mochitest/test_bug790732.html | 62 + js/xpconnect/tests/mochitest/test_bug793969.html | 53 + js/xpconnect/tests/mochitest/test_bug800864.html | 51 + js/xpconnect/tests/mochitest/test_bug802557.html | 116 + js/xpconnect/tests/mochitest/test_bug803730.html | 41 + js/xpconnect/tests/mochitest/test_bug809547.html | 42 + js/xpconnect/tests/mochitest/test_bug829872.html | 52 + js/xpconnect/tests/mochitest/test_bug862380.html | 43 + js/xpconnect/tests/mochitest/test_bug865260.html | 33 + js/xpconnect/tests/mochitest/test_bug870423.html | 51 + js/xpconnect/tests/mochitest/test_bug871887.html | 43 + js/xpconnect/tests/mochitest/test_bug912322.html | 36 + js/xpconnect/tests/mochitest/test_bug916945.html | 62 + js/xpconnect/tests/mochitest/test_bug92773.html | 43 + js/xpconnect/tests/mochitest/test_bug940783.html | 62 + js/xpconnect/tests/mochitest/test_bug960820.html | 56 + js/xpconnect/tests/mochitest/test_bug965082.html | 39 + js/xpconnect/tests/mochitest/test_bug986542.html | 45 + js/xpconnect/tests/mochitest/test_bug993423.html | 47 + .../tests/mochitest/test_crossOriginObjects.html | 336 + .../mochitest/test_crosscompartment_weakmap.html | 44 + .../tests/mochitest/test_frameWrapping.html | 37 + .../tests/mochitest/test_getWebIDLCaller.html | 49 + js/xpconnect/tests/mochitest/test_nac.xhtml | 64 + .../tests/mochitest/test_sameOriginPolicy.html | 109 + .../tests/mochitest/test_sandbox_fetch.html | 54 + js/xpconnect/tests/moz.build | 31 + .../tests/unit/CatRegistrationComponents.manifest | 2 + js/xpconnect/tests/unit/bogus_element_type.jsm | 1 + js/xpconnect/tests/unit/bogus_exports_type.jsm | 1 + js/xpconnect/tests/unit/bug451678_subscript.js | 5 + js/xpconnect/tests/unit/component-blob.js | 79 + js/xpconnect/tests/unit/component-blob.manifest | 2 + js/xpconnect/tests/unit/component-file.js | 104 + js/xpconnect/tests/unit/component-file.manifest | 2 + js/xpconnect/tests/unit/component_import.js | 86 + js/xpconnect/tests/unit/component_import.manifest | 5 + js/xpconnect/tests/unit/head_ongc.js | 41 + js/xpconnect/tests/unit/head_watchdog.js | 112 + js/xpconnect/tests/unit/importer.jsm | 1 + js/xpconnect/tests/unit/recursive_importA.jsm | 12 + js/xpconnect/tests/unit/recursive_importB.jsm | 13 + js/xpconnect/tests/unit/subScriptWithEarlyError.js | 1 + js/xpconnect/tests/unit/syntax_error.jsm | 1 + js/xpconnect/tests/unit/test_URLSearchParams.js | 13 + js/xpconnect/tests/unit/test_allowWaivers.js | 30 + js/xpconnect/tests/unit/test_allowedDomains.js | 47 + js/xpconnect/tests/unit/test_allowedDomainsXHR.js | 136 + .../tests/unit/test_asyncLoadSubScriptError.js | 33 + js/xpconnect/tests/unit/test_attributes.js | 81 + js/xpconnect/tests/unit/test_blob.js | 16 + js/xpconnect/tests/unit/test_blob2.js | 36 + js/xpconnect/tests/unit/test_bogus_files.js | 42 + js/xpconnect/tests/unit/test_bug1001094.js | 4 + js/xpconnect/tests/unit/test_bug1021312.js | 16 + js/xpconnect/tests/unit/test_bug1033253.js | 6 + js/xpconnect/tests/unit/test_bug1033920.js | 7 + js/xpconnect/tests/unit/test_bug1033927.js | 8 + js/xpconnect/tests/unit/test_bug1034262.js | 9 + js/xpconnect/tests/unit/test_bug1081990.js | 10 + js/xpconnect/tests/unit/test_bug1082450.js | 40 + js/xpconnect/tests/unit/test_bug1110546.js | 5 + js/xpconnect/tests/unit/test_bug1131707.js | 22 + js/xpconnect/tests/unit/test_bug1150106.js | 35 + js/xpconnect/tests/unit/test_bug1150771.js | 13 + js/xpconnect/tests/unit/test_bug1151385.js | 9 + js/xpconnect/tests/unit/test_bug1170311.js | 5 + js/xpconnect/tests/unit/test_bug1244222.js | 27 + js/xpconnect/tests/unit/test_bug408412.js | 17 + js/xpconnect/tests/unit/test_bug451678.js | 18 + js/xpconnect/tests/unit/test_bug604362.js | 12 + js/xpconnect/tests/unit/test_bug677864.js | 11 + js/xpconnect/tests/unit/test_bug711404.js | 10 + js/xpconnect/tests/unit/test_bug742444.js | 17 + js/xpconnect/tests/unit/test_bug778409.js | 11 + js/xpconnect/tests/unit/test_bug780370.js | 23 + js/xpconnect/tests/unit/test_bug809652.js | 63 + js/xpconnect/tests/unit/test_bug809674.js | 31 + js/xpconnect/tests/unit/test_bug813901.js | 25 + js/xpconnect/tests/unit/test_bug845201.js | 18 + js/xpconnect/tests/unit/test_bug845862.js | 13 + js/xpconnect/tests/unit/test_bug849730.js | 7 + js/xpconnect/tests/unit/test_bug851895.js | 11 + js/xpconnect/tests/unit/test_bug853709.js | 32 + js/xpconnect/tests/unit/test_bug854558.js | 11 + js/xpconnect/tests/unit/test_bug856067.js | 10 + js/xpconnect/tests/unit/test_bug867486.js | 10 + js/xpconnect/tests/unit/test_bug868675.js | 25 + js/xpconnect/tests/unit/test_bug872772.js | 43 + js/xpconnect/tests/unit/test_bug885800.js | 13 + js/xpconnect/tests/unit/test_bug930091.js | 29 + js/xpconnect/tests/unit/test_bug976151.js | 24 + js/xpconnect/tests/unit/test_bug_442086.js | 36 + .../tests/unit/test_callFunctionWithAsyncStack.js | 30 + .../tests/unit/test_classesByID_instanceof.js | 79 + js/xpconnect/tests/unit/test_components.js | 54 + js/xpconnect/tests/unit/test_crypto.js | 28 + js/xpconnect/tests/unit/test_css.js | 10 + js/xpconnect/tests/unit/test_deepFreezeClone.js | 33 + js/xpconnect/tests/unit/test_exportFunction.js | 150 + js/xpconnect/tests/unit/test_file.js | 15 + js/xpconnect/tests/unit/test_file2.js | 62 + js/xpconnect/tests/unit/test_fileReader.js | 13 + js/xpconnect/tests/unit/test_getObjectPrincipal.js | 11 + js/xpconnect/tests/unit/test_import.js | 91 + js/xpconnect/tests/unit/test_import_fail.js | 10 + js/xpconnect/tests/unit/test_interposition.js | 165 + js/xpconnect/tests/unit/test_isModuleLoaded.js | 33 + js/xpconnect/tests/unit/test_isProxy.js | 29 + js/xpconnect/tests/unit/test_js_weak_references.js | 45 + js/xpconnect/tests/unit/test_localeCompare.js | 6 + js/xpconnect/tests/unit/test_nuke_sandbox.js | 29 + .../tests/unit/test_onGarbageCollection-01.js | 64 + .../tests/unit/test_onGarbageCollection-02.js | 94 + .../tests/unit/test_onGarbageCollection-03.js | 35 + .../tests/unit/test_onGarbageCollection-04.js | 67 + .../tests/unit/test_onGarbageCollection-05.js | 37 + js/xpconnect/tests/unit/test_params.js | 201 + js/xpconnect/tests/unit/test_promise.js | 8 + js/xpconnect/tests/unit/test_recursive_import.js | 17 + js/xpconnect/tests/unit/test_reflect_parse.js | 27 + .../tests/unit/test_resolve_dead_promise.js | 39 + js/xpconnect/tests/unit/test_returncode.js | 78 + .../tests/unit/test_rtcIdentityProvider.js | 35 + js/xpconnect/tests/unit/test_sandbox_atob.js | 10 + js/xpconnect/tests/unit/test_sandbox_metadata.js | 58 + js/xpconnect/tests/unit/test_sandbox_name.js | 28 + js/xpconnect/tests/unit/test_tearoffs.js | 108 + js/xpconnect/tests/unit/test_textDecoder.js | 12 + js/xpconnect/tests/unit/test_unload.js | 28 + js/xpconnect/tests/unit/test_url.js | 10 + js/xpconnect/tests/unit/test_want_components.js | 8 + js/xpconnect/tests/unit/test_watchdog_default.js | 12 + js/xpconnect/tests/unit/test_watchdog_disable.js | 11 + js/xpconnect/tests/unit/test_watchdog_enable.js | 11 + js/xpconnect/tests/unit/test_watchdog_hibernate.js | 53 + js/xpconnect/tests/unit/test_watchdog_toggle.js | 13 + js/xpconnect/tests/unit/test_weak_keys.js | 45 + .../tests/unit/test_writeToGlobalPrototype.js | 76 + js/xpconnect/tests/unit/test_xpcomutils.js | 246 + js/xpconnect/tests/unit/test_xpcwn_tamperproof.js | 171 + js/xpconnect/tests/unit/test_xray_SavedFrame-02.js | 75 + js/xpconnect/tests/unit/test_xray_SavedFrame.js | 108 + js/xpconnect/tests/unit/test_xrayed_iterator.js | 36 + js/xpconnect/tests/unit/xpcshell.ini | 137 + js/xpconnect/wrappers/AccessCheck.cpp | 458 + js/xpconnect/wrappers/AccessCheck.h | 106 + js/xpconnect/wrappers/AddonWrapper.cpp | 270 + js/xpconnect/wrappers/AddonWrapper.h | 55 + js/xpconnect/wrappers/ChromeObjectWrapper.cpp | 41 + js/xpconnect/wrappers/ChromeObjectWrapper.h | 43 + js/xpconnect/wrappers/FilteringWrapper.cpp | 312 + js/xpconnect/wrappers/FilteringWrapper.h | 91 + js/xpconnect/wrappers/WaiveXrayWrapper.cpp | 105 + js/xpconnect/wrappers/WaiveXrayWrapper.h | 48 + js/xpconnect/wrappers/WrapperFactory.cpp | 671 + js/xpconnect/wrappers/WrapperFactory.h | 68 + js/xpconnect/wrappers/XrayWrapper.cpp | 2466 ++ js/xpconnect/wrappers/XrayWrapper.h | 620 + js/xpconnect/wrappers/moz.build | 41 + 504 files changed, 86697 insertions(+) create mode 100644 js/xpconnect/crashtests/117307-1.html create mode 100644 js/xpconnect/crashtests/193710.html create mode 100644 js/xpconnect/crashtests/290162-1.html create mode 100644 js/xpconnect/crashtests/326615-1.html create mode 100644 js/xpconnect/crashtests/328553-1.html create mode 100644 js/xpconnect/crashtests/346258-1.html create mode 100644 js/xpconnect/crashtests/346512-1-frame1.xhtml create mode 100644 js/xpconnect/crashtests/346512-1-frame2.xhtml create mode 100644 js/xpconnect/crashtests/346512-1.xhtml create mode 100644 js/xpconnect/crashtests/382133-1.html create mode 100644 js/xpconnect/crashtests/386680-1.html create mode 100644 js/xpconnect/crashtests/394810-1.html create mode 100644 js/xpconnect/crashtests/400349-1.html create mode 100644 js/xpconnect/crashtests/403356-1.html create mode 100644 js/xpconnect/crashtests/418139-1.svg create mode 100644 js/xpconnect/crashtests/420513-1.html create mode 100644 js/xpconnect/crashtests/453935-1.html create mode 100644 js/xpconnect/crashtests/462926.html create mode 100644 js/xpconnect/crashtests/467693-1.html create mode 100644 js/xpconnect/crashtests/468552-1.html create mode 100644 js/xpconnect/crashtests/471366-1.html create mode 100644 js/xpconnect/crashtests/475185-1.html create mode 100644 js/xpconnect/crashtests/475291-1.html create mode 100644 js/xpconnect/crashtests/503286-1.html create mode 100644 js/xpconnect/crashtests/504000-1.html create mode 100644 js/xpconnect/crashtests/509075-1.html create mode 100644 js/xpconnect/crashtests/512815-1.html create mode 100644 js/xpconnect/crashtests/515726-1.html create mode 100644 js/xpconnect/crashtests/545291-1.html create mode 100644 js/xpconnect/crashtests/558979.html create mode 100644 js/xpconnect/crashtests/582649.html create mode 100644 js/xpconnect/crashtests/601284-1.html create mode 100644 js/xpconnect/crashtests/603146-1.html create mode 100644 js/xpconnect/crashtests/603858-1.html create mode 100644 js/xpconnect/crashtests/608963.html create mode 100644 js/xpconnect/crashtests/616930-1.html create mode 100644 js/xpconnect/crashtests/639737-1.html create mode 100644 js/xpconnect/crashtests/648206-1.html create mode 100644 js/xpconnect/crashtests/705875.html create mode 100644 js/xpconnect/crashtests/720305-1.html create mode 100644 js/xpconnect/crashtests/723465.html create mode 100644 js/xpconnect/crashtests/732870.html create mode 100644 js/xpconnect/crashtests/751995.html create mode 100644 js/xpconnect/crashtests/752038-iframe.html create mode 100644 js/xpconnect/crashtests/752038.html create mode 100644 js/xpconnect/crashtests/753162.html create mode 100644 js/xpconnect/crashtests/754311-iframe.html create mode 100644 js/xpconnect/crashtests/754311.html create mode 100644 js/xpconnect/crashtests/761831.html create mode 100644 js/xpconnect/crashtests/786142-iframe.html create mode 100644 js/xpconnect/crashtests/786142.html create mode 100644 js/xpconnect/crashtests/797583.html create mode 100644 js/xpconnect/crashtests/806751.html create mode 100644 js/xpconnect/crashtests/833856.html create mode 100644 js/xpconnect/crashtests/851418.html create mode 100644 js/xpconnect/crashtests/854139.html create mode 100644 js/xpconnect/crashtests/854604.html create mode 100644 js/xpconnect/crashtests/898939.html create mode 100644 js/xpconnect/crashtests/905523.html create mode 100644 js/xpconnect/crashtests/938297.html create mode 100644 js/xpconnect/crashtests/crashtests.list create mode 100644 js/xpconnect/idl/moz.build create mode 100644 js/xpconnect/idl/mozIJSSubScriptLoader.idl create mode 100644 js/xpconnect/idl/nsIAddonInterposition.idl create mode 100644 js/xpconnect/idl/nsIScriptError.idl create mode 100644 js/xpconnect/idl/nsIXPCScriptable.idl create mode 100644 js/xpconnect/idl/nsIXPConnect.idl create mode 100644 js/xpconnect/idl/xpcIJSGetFactory.idl create mode 100644 js/xpconnect/idl/xpcIJSModuleLoader.idl create mode 100644 js/xpconnect/idl/xpcIJSWeakReference.idl create mode 100644 js/xpconnect/idl/xpccomponents.idl create mode 100644 js/xpconnect/idl/xpcexception.idl create mode 100644 js/xpconnect/idl/xpcjsid.idl create mode 100644 js/xpconnect/loader/ISO8601DateUtils.jsm create mode 100644 js/xpconnect/loader/XPCOMUtils.jsm create mode 100644 js/xpconnect/loader/moz.build create mode 100644 js/xpconnect/loader/mozJSComponentLoader.cpp create mode 100644 js/xpconnect/loader/mozJSComponentLoader.h create mode 100644 js/xpconnect/loader/mozJSLoaderUtils.cpp create mode 100644 js/xpconnect/loader/mozJSLoaderUtils.h create mode 100644 js/xpconnect/loader/mozJSSubScriptLoader.cpp create mode 100644 js/xpconnect/loader/mozJSSubScriptLoader.h create mode 100644 js/xpconnect/moz.build create mode 100644 js/xpconnect/public/moz.build create mode 100644 js/xpconnect/public/nsAXPCNativeCallContext.h create mode 100644 js/xpconnect/public/nsTArrayHelpers.h create mode 100644 js/xpconnect/public/xpc_make_class.h create mode 100644 js/xpconnect/public/xpc_map_end.h create mode 100644 js/xpconnect/shell/Makefile.in create mode 100644 js/xpconnect/shell/moz.build create mode 100644 js/xpconnect/shell/xpcshell.cpp create mode 100644 js/xpconnect/shell/xpcshell.exe.manifest create mode 100644 js/xpconnect/shell/xpcshell.rc create mode 100644 js/xpconnect/shell/xpcshellMacUtils.h create mode 100644 js/xpconnect/shell/xpcshellMacUtils.mm create mode 100644 js/xpconnect/src/BackstagePass.h create mode 100644 js/xpconnect/src/ExportHelpers.cpp create mode 100644 js/xpconnect/src/README create mode 100644 js/xpconnect/src/Sandbox.cpp create mode 100644 js/xpconnect/src/SandboxPrivate.h create mode 100644 js/xpconnect/src/XPCCallContext.cpp create mode 100644 js/xpconnect/src/XPCComponents.cpp create mode 100644 js/xpconnect/src/XPCConvert.cpp create mode 100644 js/xpconnect/src/XPCDebug.cpp create mode 100644 js/xpconnect/src/XPCException.cpp create mode 100644 js/xpconnect/src/XPCForwards.h create mode 100644 js/xpconnect/src/XPCInlines.h create mode 100644 js/xpconnect/src/XPCJSContext.cpp create mode 100644 js/xpconnect/src/XPCJSID.cpp create mode 100644 js/xpconnect/src/XPCJSMemoryReporter.h create mode 100644 js/xpconnect/src/XPCJSWeakReference.cpp create mode 100644 js/xpconnect/src/XPCJSWeakReference.h create mode 100644 js/xpconnect/src/XPCLocale.cpp create mode 100644 js/xpconnect/src/XPCLog.cpp create mode 100644 js/xpconnect/src/XPCLog.h create mode 100644 js/xpconnect/src/XPCMaps.cpp create mode 100644 js/xpconnect/src/XPCMaps.h create mode 100644 js/xpconnect/src/XPCModule.cpp create mode 100644 js/xpconnect/src/XPCModule.h create mode 100644 js/xpconnect/src/XPCRuntimeService.cpp create mode 100644 js/xpconnect/src/XPCShellImpl.cpp create mode 100644 js/xpconnect/src/XPCString.cpp create mode 100644 js/xpconnect/src/XPCThrower.cpp create mode 100644 js/xpconnect/src/XPCVariant.cpp create mode 100644 js/xpconnect/src/XPCWrappedJS.cpp create mode 100644 js/xpconnect/src/XPCWrappedJSClass.cpp create mode 100644 js/xpconnect/src/XPCWrappedNative.cpp create mode 100644 js/xpconnect/src/XPCWrappedNativeInfo.cpp create mode 100644 js/xpconnect/src/XPCWrappedNativeJSOps.cpp create mode 100644 js/xpconnect/src/XPCWrappedNativeProto.cpp create mode 100644 js/xpconnect/src/XPCWrappedNativeScope.cpp create mode 100644 js/xpconnect/src/XPCWrapper.cpp create mode 100644 js/xpconnect/src/XPCWrapper.h create mode 100644 js/xpconnect/src/jsshell.msg create mode 100644 js/xpconnect/src/moz.build create mode 100644 js/xpconnect/src/nsScriptError.cpp create mode 100644 js/xpconnect/src/nsScriptErrorWithStack.cpp create mode 100644 js/xpconnect/src/nsXPConnect.cpp create mode 100644 js/xpconnect/src/qsObjectHelper.h create mode 100644 js/xpconnect/src/xpc.msg create mode 100644 js/xpconnect/src/xpcObjectHelper.h create mode 100644 js/xpconnect/src/xpcprivate.h create mode 100644 js/xpconnect/src/xpcpublic.h create mode 100644 js/xpconnect/tests/browser/browser.ini create mode 100644 js/xpconnect/tests/browser/browser_deadObjectOnUnload.html create mode 100644 js/xpconnect/tests/browser/browser_dead_object.js create mode 100644 js/xpconnect/tests/browser/moz.build create mode 100644 js/xpconnect/tests/chrome/bug503926.xul create mode 100644 js/xpconnect/tests/chrome/chrome.ini create mode 100644 js/xpconnect/tests/chrome/file_bug1050049.xml create mode 100644 js/xpconnect/tests/chrome/file_bug1281071.html create mode 100644 js/xpconnect/tests/chrome/file_bug618176.xul create mode 100644 js/xpconnect/tests/chrome/file_bug996069.html create mode 100644 js/xpconnect/tests/chrome/file_discardSystemSource.html create mode 100644 js/xpconnect/tests/chrome/file_evalInSandbox.html create mode 100644 js/xpconnect/tests/chrome/file_expandosharing.jsm create mode 100644 js/xpconnect/tests/chrome/moz.build create mode 100644 js/xpconnect/tests/chrome/outoflinexulscript.js create mode 100644 js/xpconnect/tests/chrome/subscript.js create mode 100644 js/xpconnect/tests/chrome/test_APIExposer.xul create mode 100644 js/xpconnect/tests/chrome/test_bug1041626.xul create mode 100644 js/xpconnect/tests/chrome/test_bug1042436.xul create mode 100644 js/xpconnect/tests/chrome/test_bug1050049.html create mode 100644 js/xpconnect/tests/chrome/test_bug1065185.html create mode 100644 js/xpconnect/tests/chrome/test_bug1074863.html create mode 100644 js/xpconnect/tests/chrome/test_bug1092477.xul create mode 100644 js/xpconnect/tests/chrome/test_bug1124898.html create mode 100644 js/xpconnect/tests/chrome/test_bug1126911.html create mode 100644 js/xpconnect/tests/chrome/test_bug1281071.xul create mode 100644 js/xpconnect/tests/chrome/test_bug361111.xul create mode 100644 js/xpconnect/tests/chrome/test_bug448587.xul create mode 100644 js/xpconnect/tests/chrome/test_bug484459.xul create mode 100644 js/xpconnect/tests/chrome/test_bug500931.xul create mode 100644 js/xpconnect/tests/chrome/test_bug503926.xul create mode 100644 js/xpconnect/tests/chrome/test_bug533596.xul create mode 100644 js/xpconnect/tests/chrome/test_bug571849.xul create mode 100644 js/xpconnect/tests/chrome/test_bug596580.xul create mode 100644 js/xpconnect/tests/chrome/test_bug601803.xul create mode 100644 js/xpconnect/tests/chrome/test_bug610390.xul create mode 100644 js/xpconnect/tests/chrome/test_bug614757.xul create mode 100644 js/xpconnect/tests/chrome/test_bug616992.xul create mode 100644 js/xpconnect/tests/chrome/test_bug618176.xul create mode 100644 js/xpconnect/tests/chrome/test_bug654370.xul create mode 100644 js/xpconnect/tests/chrome/test_bug658560.xul create mode 100644 js/xpconnect/tests/chrome/test_bug658909.xul create mode 100644 js/xpconnect/tests/chrome/test_bug664689.xul create mode 100644 js/xpconnect/tests/chrome/test_bug679861.xul create mode 100644 js/xpconnect/tests/chrome/test_bug706301.xul create mode 100644 js/xpconnect/tests/chrome/test_bug720619.xul create mode 100644 js/xpconnect/tests/chrome/test_bug726949.xul create mode 100644 js/xpconnect/tests/chrome/test_bug732665.xul create mode 100644 js/xpconnect/tests/chrome/test_bug732665_meta.js create mode 100644 js/xpconnect/tests/chrome/test_bug738244.xul create mode 100644 js/xpconnect/tests/chrome/test_bug743843.xul create mode 100644 js/xpconnect/tests/chrome/test_bug760076.xul create mode 100644 js/xpconnect/tests/chrome/test_bug760131.html create mode 100644 js/xpconnect/tests/chrome/test_bug763343.xul create mode 100644 js/xpconnect/tests/chrome/test_bug771429.xul create mode 100644 js/xpconnect/tests/chrome/test_bug773962.xul create mode 100644 js/xpconnect/tests/chrome/test_bug792280.xul create mode 100644 js/xpconnect/tests/chrome/test_bug793433.xul create mode 100644 js/xpconnect/tests/chrome/test_bug795275.xul create mode 100644 js/xpconnect/tests/chrome/test_bug799348.xul create mode 100644 js/xpconnect/tests/chrome/test_bug801241.xul create mode 100644 js/xpconnect/tests/chrome/test_bug812415.xul create mode 100644 js/xpconnect/tests/chrome/test_bug853283.xul create mode 100644 js/xpconnect/tests/chrome/test_bug853571.xul create mode 100644 js/xpconnect/tests/chrome/test_bug858101.xul create mode 100644 js/xpconnect/tests/chrome/test_bug860494.xul create mode 100644 js/xpconnect/tests/chrome/test_bug865948.xul create mode 100644 js/xpconnect/tests/chrome/test_bug866823.xul create mode 100644 js/xpconnect/tests/chrome/test_bug895340.xul create mode 100644 js/xpconnect/tests/chrome/test_bug932906.xul create mode 100644 js/xpconnect/tests/chrome/test_bug996069.xul create mode 100644 js/xpconnect/tests/chrome/test_chrometoSource.xul create mode 100644 js/xpconnect/tests/chrome/test_cloneInto.xul create mode 100644 js/xpconnect/tests/chrome/test_cows.xul create mode 100644 js/xpconnect/tests/chrome/test_discardSystemSource.xul create mode 100644 js/xpconnect/tests/chrome/test_documentdomain.xul create mode 100644 js/xpconnect/tests/chrome/test_doublewrappedcompartments.xul create mode 100644 js/xpconnect/tests/chrome/test_evalInSandbox.xul create mode 100644 js/xpconnect/tests/chrome/test_evalInWindow.xul create mode 100644 js/xpconnect/tests/chrome/test_exnstack.xul create mode 100644 js/xpconnect/tests/chrome/test_expandosharing.xul create mode 100644 js/xpconnect/tests/chrome/test_exposeInDerived.xul create mode 100644 js/xpconnect/tests/chrome/test_getweakmapkeys.xul create mode 100644 js/xpconnect/tests/chrome/test_localstorage_with_nsEp.xul create mode 100644 js/xpconnect/tests/chrome/test_matches.xul create mode 100644 js/xpconnect/tests/chrome/test_nodelists.xul create mode 100644 js/xpconnect/tests/chrome/test_nsScriptErrorWithStack.html create mode 100644 js/xpconnect/tests/chrome/test_onGarbageCollection.html create mode 100644 js/xpconnect/tests/chrome/test_paris_weakmap_keys.xul create mode 100644 js/xpconnect/tests/chrome/test_precisegc.xul create mode 100644 js/xpconnect/tests/chrome/test_sandboxImport.xul create mode 100644 js/xpconnect/tests/chrome/test_scriptSettings.xul create mode 100644 js/xpconnect/tests/chrome/test_watchpoints.xul create mode 100644 js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xul create mode 100644 js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xul create mode 100644 js/xpconnect/tests/chrome/test_weakmaps.xul create mode 100644 js/xpconnect/tests/chrome/test_weakref.xul create mode 100644 js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html create mode 100644 js/xpconnect/tests/chrome/test_wrappers-2.xul create mode 100644 js/xpconnect/tests/chrome/test_wrappers.xul create mode 100644 js/xpconnect/tests/chrome/test_xrayToJS.xul create mode 100644 js/xpconnect/tests/chrome/utf8_subscript.js create mode 100644 js/xpconnect/tests/chrome/worker_discardSystemSource.js create mode 100644 js/xpconnect/tests/components/js/xpctest.manifest create mode 100644 js/xpconnect/tests/components/js/xpctest_attributes.js create mode 100644 js/xpconnect/tests/components/js/xpctest_bug809674.js create mode 100644 js/xpconnect/tests/components/js/xpctest_interfaces.js create mode 100644 js/xpconnect/tests/components/js/xpctest_params.js create mode 100644 js/xpconnect/tests/components/js/xpctest_returncode_child.js create mode 100644 js/xpconnect/tests/components/js/xpctest_utils.js create mode 100644 js/xpconnect/tests/components/native/moz.build create mode 100644 js/xpconnect/tests/components/native/xpctest.manifest create mode 100644 js/xpconnect/tests/components/native/xpctest_attributes.cpp create mode 100644 js/xpconnect/tests/components/native/xpctest_module.cpp create mode 100644 js/xpconnect/tests/components/native/xpctest_params.cpp create mode 100644 js/xpconnect/tests/components/native/xpctest_private.h create mode 100644 js/xpconnect/tests/components/native/xpctest_returncode.cpp create mode 100644 js/xpconnect/tests/idl/moz.build create mode 100644 js/xpconnect/tests/idl/xpctest_attributes.idl create mode 100644 js/xpconnect/tests/idl/xpctest_bug809674.idl create mode 100644 js/xpconnect/tests/idl/xpctest_interfaces.idl create mode 100644 js/xpconnect/tests/idl/xpctest_params.idl create mode 100644 js/xpconnect/tests/idl/xpctest_returncode.idl create mode 100644 js/xpconnect/tests/idl/xpctest_utils.idl create mode 100644 js/xpconnect/tests/mochitest/bug500931_helper.html create mode 100644 js/xpconnect/tests/mochitest/bug504877_helper.html create mode 100644 js/xpconnect/tests/mochitest/bug571849_helper.html create mode 100644 js/xpconnect/tests/mochitest/bug589028_helper.html create mode 100644 js/xpconnect/tests/mochitest/bug92773_helper.html create mode 100644 js/xpconnect/tests/mochitest/chrome_wrappers_helper.html create mode 100644 js/xpconnect/tests/mochitest/file1_bug629227.html create mode 100644 js/xpconnect/tests/mochitest/file2_bug629227.html create mode 100644 js/xpconnect/tests/mochitest/file_bug505915.html create mode 100644 js/xpconnect/tests/mochitest/file_bug650273.html create mode 100644 js/xpconnect/tests/mochitest/file_bug658560.html create mode 100644 js/xpconnect/tests/mochitest/file_bug706301.html create mode 100644 js/xpconnect/tests/mochitest/file_bug720619.html create mode 100644 js/xpconnect/tests/mochitest/file_bug738244.html create mode 100644 js/xpconnect/tests/mochitest/file_bug760131.html create mode 100644 js/xpconnect/tests/mochitest/file_bug781476.html create mode 100644 js/xpconnect/tests/mochitest/file_bug789713.html create mode 100644 js/xpconnect/tests/mochitest/file_bug795275.html create mode 100644 js/xpconnect/tests/mochitest/file_bug795275.xml create mode 100644 js/xpconnect/tests/mochitest/file_bug799348.html create mode 100644 js/xpconnect/tests/mochitest/file_bug802557.html create mode 100644 js/xpconnect/tests/mochitest/file_bug860494.html create mode 100644 js/xpconnect/tests/mochitest/file_crossOriginObjects.html create mode 100644 js/xpconnect/tests/mochitest/file_crossOriginObjects_documentDomain.html create mode 100644 js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html create mode 100644 js/xpconnect/tests/mochitest/file_documentdomain.html create mode 100644 js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html create mode 100644 js/xpconnect/tests/mochitest/file_empty.html create mode 100644 js/xpconnect/tests/mochitest/file_evalInSandbox.html create mode 100644 js/xpconnect/tests/mochitest/file_exnstack.html create mode 100644 js/xpconnect/tests/mochitest/file_expandosharing.html create mode 100644 js/xpconnect/tests/mochitest/file_matches.html create mode 100644 js/xpconnect/tests/mochitest/file_nodelists.html create mode 100644 js/xpconnect/tests/mochitest/file_wrappers-2.html create mode 100644 js/xpconnect/tests/mochitest/inner.html create mode 100644 js/xpconnect/tests/mochitest/mochitest.ini create mode 100644 js/xpconnect/tests/mochitest/moz.build create mode 100644 js/xpconnect/tests/mochitest/test1_bug629331.html create mode 100644 js/xpconnect/tests/mochitest/test2_bug629331.html create mode 100644 js/xpconnect/tests/mochitest/test_bug1005806.html create mode 100644 js/xpconnect/tests/mochitest/test_bug1094930.html create mode 100644 js/xpconnect/tests/mochitest/test_bug1158558.html create mode 100644 js/xpconnect/tests/mochitest/test_bug384632.html create mode 100644 js/xpconnect/tests/mochitest/test_bug390488.html create mode 100644 js/xpconnect/tests/mochitest/test_bug393269.html create mode 100644 js/xpconnect/tests/mochitest/test_bug396851.html create mode 100644 js/xpconnect/tests/mochitest/test_bug428021.html create mode 100644 js/xpconnect/tests/mochitest/test_bug446584.html create mode 100644 js/xpconnect/tests/mochitest/test_bug462428.html create mode 100644 js/xpconnect/tests/mochitest/test_bug478438.html create mode 100644 js/xpconnect/tests/mochitest/test_bug484107.html create mode 100644 js/xpconnect/tests/mochitest/test_bug500691.html create mode 100644 js/xpconnect/tests/mochitest/test_bug504877.html create mode 100644 js/xpconnect/tests/mochitest/test_bug505915.html create mode 100644 js/xpconnect/tests/mochitest/test_bug560351.html create mode 100644 js/xpconnect/tests/mochitest/test_bug585745.html create mode 100644 js/xpconnect/tests/mochitest/test_bug589028.html create mode 100644 js/xpconnect/tests/mochitest/test_bug601299.html create mode 100644 js/xpconnect/tests/mochitest/test_bug605167.html create mode 100644 js/xpconnect/tests/mochitest/test_bug618017.html create mode 100644 js/xpconnect/tests/mochitest/test_bug623437.html create mode 100644 js/xpconnect/tests/mochitest/test_bug628410.html create mode 100644 js/xpconnect/tests/mochitest/test_bug628794.html create mode 100644 js/xpconnect/tests/mochitest/test_bug629227.html create mode 100644 js/xpconnect/tests/mochitest/test_bug629331.html create mode 100644 js/xpconnect/tests/mochitest/test_bug636097.html create mode 100644 js/xpconnect/tests/mochitest/test_bug650273.html create mode 100644 js/xpconnect/tests/mochitest/test_bug655297-1.html create mode 100644 js/xpconnect/tests/mochitest/test_bug655297-2.html create mode 100644 js/xpconnect/tests/mochitest/test_bug661980.html create mode 100644 js/xpconnect/tests/mochitest/test_bug691059.html create mode 100644 js/xpconnect/tests/mochitest/test_bug720619.html create mode 100644 js/xpconnect/tests/mochitest/test_bug731471.html create mode 100644 js/xpconnect/tests/mochitest/test_bug764389.html create mode 100644 js/xpconnect/tests/mochitest/test_bug772288.html create mode 100644 js/xpconnect/tests/mochitest/test_bug781476.html create mode 100644 js/xpconnect/tests/mochitest/test_bug789713.html create mode 100644 js/xpconnect/tests/mochitest/test_bug790732.html create mode 100644 js/xpconnect/tests/mochitest/test_bug793969.html create mode 100644 js/xpconnect/tests/mochitest/test_bug800864.html create mode 100644 js/xpconnect/tests/mochitest/test_bug802557.html create mode 100644 js/xpconnect/tests/mochitest/test_bug803730.html create mode 100644 js/xpconnect/tests/mochitest/test_bug809547.html create mode 100644 js/xpconnect/tests/mochitest/test_bug829872.html create mode 100644 js/xpconnect/tests/mochitest/test_bug862380.html create mode 100644 js/xpconnect/tests/mochitest/test_bug865260.html create mode 100644 js/xpconnect/tests/mochitest/test_bug870423.html create mode 100644 js/xpconnect/tests/mochitest/test_bug871887.html create mode 100644 js/xpconnect/tests/mochitest/test_bug912322.html create mode 100644 js/xpconnect/tests/mochitest/test_bug916945.html create mode 100644 js/xpconnect/tests/mochitest/test_bug92773.html create mode 100644 js/xpconnect/tests/mochitest/test_bug940783.html create mode 100644 js/xpconnect/tests/mochitest/test_bug960820.html create mode 100644 js/xpconnect/tests/mochitest/test_bug965082.html create mode 100644 js/xpconnect/tests/mochitest/test_bug986542.html create mode 100644 js/xpconnect/tests/mochitest/test_bug993423.html create mode 100644 js/xpconnect/tests/mochitest/test_crossOriginObjects.html create mode 100644 js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html create mode 100644 js/xpconnect/tests/mochitest/test_frameWrapping.html create mode 100644 js/xpconnect/tests/mochitest/test_getWebIDLCaller.html create mode 100644 js/xpconnect/tests/mochitest/test_nac.xhtml create mode 100644 js/xpconnect/tests/mochitest/test_sameOriginPolicy.html create mode 100644 js/xpconnect/tests/mochitest/test_sandbox_fetch.html create mode 100644 js/xpconnect/tests/moz.build create mode 100644 js/xpconnect/tests/unit/CatRegistrationComponents.manifest create mode 100644 js/xpconnect/tests/unit/bogus_element_type.jsm create mode 100644 js/xpconnect/tests/unit/bogus_exports_type.jsm create mode 100644 js/xpconnect/tests/unit/bug451678_subscript.js create mode 100644 js/xpconnect/tests/unit/component-blob.js create mode 100644 js/xpconnect/tests/unit/component-blob.manifest create mode 100644 js/xpconnect/tests/unit/component-file.js create mode 100644 js/xpconnect/tests/unit/component-file.manifest create mode 100644 js/xpconnect/tests/unit/component_import.js create mode 100644 js/xpconnect/tests/unit/component_import.manifest create mode 100644 js/xpconnect/tests/unit/head_ongc.js create mode 100644 js/xpconnect/tests/unit/head_watchdog.js create mode 100644 js/xpconnect/tests/unit/importer.jsm create mode 100644 js/xpconnect/tests/unit/recursive_importA.jsm create mode 100644 js/xpconnect/tests/unit/recursive_importB.jsm create mode 100644 js/xpconnect/tests/unit/subScriptWithEarlyError.js create mode 100644 js/xpconnect/tests/unit/syntax_error.jsm create mode 100644 js/xpconnect/tests/unit/test_URLSearchParams.js create mode 100644 js/xpconnect/tests/unit/test_allowWaivers.js create mode 100644 js/xpconnect/tests/unit/test_allowedDomains.js create mode 100644 js/xpconnect/tests/unit/test_allowedDomainsXHR.js create mode 100644 js/xpconnect/tests/unit/test_asyncLoadSubScriptError.js create mode 100644 js/xpconnect/tests/unit/test_attributes.js create mode 100644 js/xpconnect/tests/unit/test_blob.js create mode 100644 js/xpconnect/tests/unit/test_blob2.js create mode 100644 js/xpconnect/tests/unit/test_bogus_files.js create mode 100644 js/xpconnect/tests/unit/test_bug1001094.js create mode 100644 js/xpconnect/tests/unit/test_bug1021312.js create mode 100644 js/xpconnect/tests/unit/test_bug1033253.js create mode 100644 js/xpconnect/tests/unit/test_bug1033920.js create mode 100644 js/xpconnect/tests/unit/test_bug1033927.js create mode 100644 js/xpconnect/tests/unit/test_bug1034262.js create mode 100644 js/xpconnect/tests/unit/test_bug1081990.js create mode 100644 js/xpconnect/tests/unit/test_bug1082450.js create mode 100644 js/xpconnect/tests/unit/test_bug1110546.js create mode 100644 js/xpconnect/tests/unit/test_bug1131707.js create mode 100644 js/xpconnect/tests/unit/test_bug1150106.js create mode 100644 js/xpconnect/tests/unit/test_bug1150771.js create mode 100644 js/xpconnect/tests/unit/test_bug1151385.js create mode 100644 js/xpconnect/tests/unit/test_bug1170311.js create mode 100644 js/xpconnect/tests/unit/test_bug1244222.js create mode 100644 js/xpconnect/tests/unit/test_bug408412.js create mode 100644 js/xpconnect/tests/unit/test_bug451678.js create mode 100644 js/xpconnect/tests/unit/test_bug604362.js create mode 100644 js/xpconnect/tests/unit/test_bug677864.js create mode 100644 js/xpconnect/tests/unit/test_bug711404.js create mode 100644 js/xpconnect/tests/unit/test_bug742444.js create mode 100644 js/xpconnect/tests/unit/test_bug778409.js create mode 100644 js/xpconnect/tests/unit/test_bug780370.js create mode 100644 js/xpconnect/tests/unit/test_bug809652.js create mode 100644 js/xpconnect/tests/unit/test_bug809674.js create mode 100644 js/xpconnect/tests/unit/test_bug813901.js create mode 100644 js/xpconnect/tests/unit/test_bug845201.js create mode 100644 js/xpconnect/tests/unit/test_bug845862.js create mode 100644 js/xpconnect/tests/unit/test_bug849730.js create mode 100644 js/xpconnect/tests/unit/test_bug851895.js create mode 100644 js/xpconnect/tests/unit/test_bug853709.js create mode 100644 js/xpconnect/tests/unit/test_bug854558.js create mode 100644 js/xpconnect/tests/unit/test_bug856067.js create mode 100644 js/xpconnect/tests/unit/test_bug867486.js create mode 100644 js/xpconnect/tests/unit/test_bug868675.js create mode 100644 js/xpconnect/tests/unit/test_bug872772.js create mode 100644 js/xpconnect/tests/unit/test_bug885800.js create mode 100644 js/xpconnect/tests/unit/test_bug930091.js create mode 100644 js/xpconnect/tests/unit/test_bug976151.js create mode 100644 js/xpconnect/tests/unit/test_bug_442086.js create mode 100644 js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js create mode 100644 js/xpconnect/tests/unit/test_classesByID_instanceof.js create mode 100644 js/xpconnect/tests/unit/test_components.js create mode 100644 js/xpconnect/tests/unit/test_crypto.js create mode 100644 js/xpconnect/tests/unit/test_css.js create mode 100644 js/xpconnect/tests/unit/test_deepFreezeClone.js create mode 100644 js/xpconnect/tests/unit/test_exportFunction.js create mode 100644 js/xpconnect/tests/unit/test_file.js create mode 100644 js/xpconnect/tests/unit/test_file2.js create mode 100644 js/xpconnect/tests/unit/test_fileReader.js create mode 100644 js/xpconnect/tests/unit/test_getObjectPrincipal.js create mode 100644 js/xpconnect/tests/unit/test_import.js create mode 100644 js/xpconnect/tests/unit/test_import_fail.js create mode 100644 js/xpconnect/tests/unit/test_interposition.js create mode 100644 js/xpconnect/tests/unit/test_isModuleLoaded.js create mode 100644 js/xpconnect/tests/unit/test_isProxy.js create mode 100644 js/xpconnect/tests/unit/test_js_weak_references.js create mode 100644 js/xpconnect/tests/unit/test_localeCompare.js create mode 100644 js/xpconnect/tests/unit/test_nuke_sandbox.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-01.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-02.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-03.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-04.js create mode 100644 js/xpconnect/tests/unit/test_onGarbageCollection-05.js create mode 100644 js/xpconnect/tests/unit/test_params.js create mode 100644 js/xpconnect/tests/unit/test_promise.js create mode 100644 js/xpconnect/tests/unit/test_recursive_import.js create mode 100644 js/xpconnect/tests/unit/test_reflect_parse.js create mode 100644 js/xpconnect/tests/unit/test_resolve_dead_promise.js create mode 100644 js/xpconnect/tests/unit/test_returncode.js create mode 100644 js/xpconnect/tests/unit/test_rtcIdentityProvider.js create mode 100644 js/xpconnect/tests/unit/test_sandbox_atob.js create mode 100644 js/xpconnect/tests/unit/test_sandbox_metadata.js create mode 100644 js/xpconnect/tests/unit/test_sandbox_name.js create mode 100644 js/xpconnect/tests/unit/test_tearoffs.js create mode 100644 js/xpconnect/tests/unit/test_textDecoder.js create mode 100644 js/xpconnect/tests/unit/test_unload.js create mode 100644 js/xpconnect/tests/unit/test_url.js create mode 100644 js/xpconnect/tests/unit/test_want_components.js create mode 100644 js/xpconnect/tests/unit/test_watchdog_default.js create mode 100644 js/xpconnect/tests/unit/test_watchdog_disable.js create mode 100644 js/xpconnect/tests/unit/test_watchdog_enable.js create mode 100644 js/xpconnect/tests/unit/test_watchdog_hibernate.js create mode 100644 js/xpconnect/tests/unit/test_watchdog_toggle.js create mode 100644 js/xpconnect/tests/unit/test_weak_keys.js create mode 100644 js/xpconnect/tests/unit/test_writeToGlobalPrototype.js create mode 100644 js/xpconnect/tests/unit/test_xpcomutils.js create mode 100644 js/xpconnect/tests/unit/test_xpcwn_tamperproof.js create mode 100644 js/xpconnect/tests/unit/test_xray_SavedFrame-02.js create mode 100644 js/xpconnect/tests/unit/test_xray_SavedFrame.js create mode 100644 js/xpconnect/tests/unit/test_xrayed_iterator.js create mode 100644 js/xpconnect/tests/unit/xpcshell.ini create mode 100644 js/xpconnect/wrappers/AccessCheck.cpp create mode 100644 js/xpconnect/wrappers/AccessCheck.h create mode 100644 js/xpconnect/wrappers/AddonWrapper.cpp create mode 100644 js/xpconnect/wrappers/AddonWrapper.h create mode 100644 js/xpconnect/wrappers/ChromeObjectWrapper.cpp create mode 100644 js/xpconnect/wrappers/ChromeObjectWrapper.h create mode 100644 js/xpconnect/wrappers/FilteringWrapper.cpp create mode 100644 js/xpconnect/wrappers/FilteringWrapper.h create mode 100644 js/xpconnect/wrappers/WaiveXrayWrapper.cpp create mode 100644 js/xpconnect/wrappers/WaiveXrayWrapper.h create mode 100644 js/xpconnect/wrappers/WrapperFactory.cpp create mode 100644 js/xpconnect/wrappers/WrapperFactory.h create mode 100644 js/xpconnect/wrappers/XrayWrapper.cpp create mode 100644 js/xpconnect/wrappers/XrayWrapper.h create mode 100644 js/xpconnect/wrappers/moz.build (limited to 'js/xpconnect') diff --git a/js/xpconnect/crashtests/117307-1.html b/js/xpconnect/crashtests/117307-1.html new file mode 100644 index 000000000..427ab1655 --- /dev/null +++ b/js/xpconnect/crashtests/117307-1.html @@ -0,0 +1,20 @@ + + +Bug 117307 diff --git a/js/xpconnect/crashtests/193710.html b/js/xpconnect/crashtests/193710.html new file mode 100644 index 000000000..1320e5135 --- /dev/null +++ b/js/xpconnect/crashtests/193710.html @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/js/xpconnect/crashtests/290162-1.html b/js/xpconnect/crashtests/290162-1.html new file mode 100644 index 000000000..09be69d3b --- /dev/null +++ b/js/xpconnect/crashtests/290162-1.html @@ -0,0 +1,5 @@ + + + diff --git a/js/xpconnect/crashtests/326615-1.html b/js/xpconnect/crashtests/326615-1.html new file mode 100644 index 000000000..5e6684a19 --- /dev/null +++ b/js/xpconnect/crashtests/326615-1.html @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/js/xpconnect/crashtests/328553-1.html b/js/xpconnect/crashtests/328553-1.html new file mode 100644 index 000000000..aa539b7ba --- /dev/null +++ b/js/xpconnect/crashtests/328553-1.html @@ -0,0 +1,13 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/346258-1.html b/js/xpconnect/crashtests/346258-1.html new file mode 100644 index 000000000..c7a54d5c6 --- /dev/null +++ b/js/xpconnect/crashtests/346258-1.html @@ -0,0 +1,12 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/346512-1-frame1.xhtml b/js/xpconnect/crashtests/346512-1-frame1.xhtml new file mode 100644 index 000000000..bdc38c8a6 --- /dev/null +++ b/js/xpconnect/crashtests/346512-1-frame1.xhtml @@ -0,0 +1,16 @@ + + + + + + + + +
+ +
+ + + + + diff --git a/js/xpconnect/crashtests/346512-1-frame2.xhtml b/js/xpconnect/crashtests/346512-1-frame2.xhtml new file mode 100644 index 000000000..466712830 --- /dev/null +++ b/js/xpconnect/crashtests/346512-1-frame2.xhtml @@ -0,0 +1,15 @@ + + + + + + + +
+ + + + + + + diff --git a/js/xpconnect/crashtests/346512-1.xhtml b/js/xpconnect/crashtests/346512-1.xhtml new file mode 100644 index 000000000..1c8b6ba6d --- /dev/null +++ b/js/xpconnect/crashtests/346512-1.xhtml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/js/xpconnect/crashtests/403356-1.html b/js/xpconnect/crashtests/403356-1.html new file mode 100644 index 000000000..a6a2adbf6 --- /dev/null +++ b/js/xpconnect/crashtests/403356-1.html @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/418139-1.svg b/js/xpconnect/crashtests/418139-1.svg new file mode 100644 index 000000000..0c878f7c8 --- /dev/null +++ b/js/xpconnect/crashtests/418139-1.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/js/xpconnect/crashtests/420513-1.html b/js/xpconnect/crashtests/420513-1.html new file mode 100644 index 000000000..ed0981c1c --- /dev/null +++ b/js/xpconnect/crashtests/420513-1.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/453935-1.html b/js/xpconnect/crashtests/453935-1.html new file mode 100644 index 000000000..9c9bab3d7 --- /dev/null +++ b/js/xpconnect/crashtests/453935-1.html @@ -0,0 +1,37 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/462926.html b/js/xpconnect/crashtests/462926.html new file mode 100644 index 000000000..5fc828edf --- /dev/null +++ b/js/xpconnect/crashtests/462926.html @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/467693-1.html b/js/xpconnect/crashtests/467693-1.html new file mode 100644 index 000000000..23a4f3894 --- /dev/null +++ b/js/xpconnect/crashtests/467693-1.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/468552-1.html b/js/xpconnect/crashtests/468552-1.html new file mode 100644 index 000000000..0ce2e3eb4 --- /dev/null +++ b/js/xpconnect/crashtests/468552-1.html @@ -0,0 +1,18 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/471366-1.html b/js/xpconnect/crashtests/471366-1.html new file mode 100644 index 000000000..b450d5e12 --- /dev/null +++ b/js/xpconnect/crashtests/471366-1.html @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/475185-1.html b/js/xpconnect/crashtests/475185-1.html new file mode 100644 index 000000000..a599c37b0 --- /dev/null +++ b/js/xpconnect/crashtests/475185-1.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/js/xpconnect/crashtests/475291-1.html b/js/xpconnect/crashtests/475291-1.html new file mode 100644 index 000000000..e5658f74b --- /dev/null +++ b/js/xpconnect/crashtests/475291-1.html @@ -0,0 +1,14 @@ + + + + + + diff --git a/js/xpconnect/crashtests/503286-1.html b/js/xpconnect/crashtests/503286-1.html new file mode 100644 index 000000000..ca14f4949 --- /dev/null +++ b/js/xpconnect/crashtests/503286-1.html @@ -0,0 +1,23 @@ +Firefox 3.5 crash + + + + diff --git a/js/xpconnect/crashtests/504000-1.html b/js/xpconnect/crashtests/504000-1.html new file mode 100644 index 000000000..a909eb34a --- /dev/null +++ b/js/xpconnect/crashtests/504000-1.html @@ -0,0 +1,21 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/509075-1.html b/js/xpconnect/crashtests/509075-1.html new file mode 100644 index 000000000..77e8e62fc --- /dev/null +++ b/js/xpconnect/crashtests/509075-1.html @@ -0,0 +1,28 @@ + + + + diff --git a/js/xpconnect/crashtests/512815-1.html b/js/xpconnect/crashtests/512815-1.html new file mode 100644 index 000000000..09903ad3b --- /dev/null +++ b/js/xpconnect/crashtests/512815-1.html @@ -0,0 +1,21 @@ + + + + +
+ diff --git a/js/xpconnect/crashtests/515726-1.html b/js/xpconnect/crashtests/515726-1.html new file mode 100644 index 000000000..3bf73d42c --- /dev/null +++ b/js/xpconnect/crashtests/515726-1.html @@ -0,0 +1,26 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/545291-1.html b/js/xpconnect/crashtests/545291-1.html new file mode 100644 index 000000000..ed50900db --- /dev/null +++ b/js/xpconnect/crashtests/545291-1.html @@ -0,0 +1,12 @@ + + + + + + diff --git a/js/xpconnect/crashtests/558979.html b/js/xpconnect/crashtests/558979.html new file mode 100644 index 000000000..404748c42 --- /dev/null +++ b/js/xpconnect/crashtests/558979.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/js/xpconnect/crashtests/582649.html b/js/xpconnect/crashtests/582649.html new file mode 100644 index 000000000..235858e3c --- /dev/null +++ b/js/xpconnect/crashtests/582649.html @@ -0,0 +1,12 @@ + + + Testcase for bug 582649 + + + + + diff --git a/js/xpconnect/crashtests/601284-1.html b/js/xpconnect/crashtests/601284-1.html new file mode 100644 index 000000000..3bd3b2bef --- /dev/null +++ b/js/xpconnect/crashtests/601284-1.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + diff --git a/js/xpconnect/crashtests/603146-1.html b/js/xpconnect/crashtests/603146-1.html new file mode 100644 index 000000000..74b103004 --- /dev/null +++ b/js/xpconnect/crashtests/603146-1.html @@ -0,0 +1,7 @@ + + diff --git a/js/xpconnect/crashtests/603858-1.html b/js/xpconnect/crashtests/603858-1.html new file mode 100644 index 000000000..8a48bbda8 --- /dev/null +++ b/js/xpconnect/crashtests/603858-1.html @@ -0,0 +1,8 @@ + + + + diff --git a/js/xpconnect/crashtests/608963.html b/js/xpconnect/crashtests/608963.html new file mode 100644 index 000000000..ef2e5bbfc --- /dev/null +++ b/js/xpconnect/crashtests/608963.html @@ -0,0 +1,5 @@ + + + diff --git a/js/xpconnect/crashtests/616930-1.html b/js/xpconnect/crashtests/616930-1.html new file mode 100644 index 000000000..e34fbca6c --- /dev/null +++ b/js/xpconnect/crashtests/616930-1.html @@ -0,0 +1,15 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/639737-1.html b/js/xpconnect/crashtests/639737-1.html new file mode 100644 index 000000000..b461cc02b --- /dev/null +++ b/js/xpconnect/crashtests/639737-1.html @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/js/xpconnect/crashtests/648206-1.html b/js/xpconnect/crashtests/648206-1.html new file mode 100644 index 000000000..5f5ed4b7c --- /dev/null +++ b/js/xpconnect/crashtests/648206-1.html @@ -0,0 +1,7 @@ + + diff --git a/js/xpconnect/crashtests/705875.html b/js/xpconnect/crashtests/705875.html new file mode 100644 index 000000000..e651fb734 --- /dev/null +++ b/js/xpconnect/crashtests/705875.html @@ -0,0 +1,7 @@ + + diff --git a/js/xpconnect/crashtests/720305-1.html b/js/xpconnect/crashtests/720305-1.html new file mode 100644 index 000000000..84f1c6267 --- /dev/null +++ b/js/xpconnect/crashtests/720305-1.html @@ -0,0 +1,8 @@ + + diff --git a/js/xpconnect/crashtests/723465.html b/js/xpconnect/crashtests/723465.html new file mode 100644 index 000000000..0f810b963 --- /dev/null +++ b/js/xpconnect/crashtests/723465.html @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/732870.html b/js/xpconnect/crashtests/732870.html new file mode 100644 index 000000000..bd827bcb3 --- /dev/null +++ b/js/xpconnect/crashtests/732870.html @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/js/xpconnect/crashtests/751995.html b/js/xpconnect/crashtests/751995.html new file mode 100644 index 000000000..fce9565ef --- /dev/null +++ b/js/xpconnect/crashtests/751995.html @@ -0,0 +1,36 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/752038-iframe.html b/js/xpconnect/crashtests/752038-iframe.html new file mode 100644 index 000000000..fc14c16f8 --- /dev/null +++ b/js/xpconnect/crashtests/752038-iframe.html @@ -0,0 +1,11 @@ + + + + diff --git a/js/xpconnect/crashtests/752038.html b/js/xpconnect/crashtests/752038.html new file mode 100644 index 000000000..9da91b270 --- /dev/null +++ b/js/xpconnect/crashtests/752038.html @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/753162.html b/js/xpconnect/crashtests/753162.html new file mode 100644 index 000000000..7e53a29e5 --- /dev/null +++ b/js/xpconnect/crashtests/753162.html @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/754311-iframe.html b/js/xpconnect/crashtests/754311-iframe.html new file mode 100644 index 000000000..002817fa9 --- /dev/null +++ b/js/xpconnect/crashtests/754311-iframe.html @@ -0,0 +1,21 @@ + + + + + + + diff --git a/js/xpconnect/crashtests/754311.html b/js/xpconnect/crashtests/754311.html new file mode 100644 index 000000000..36d603fd7 --- /dev/null +++ b/js/xpconnect/crashtests/754311.html @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/761831.html b/js/xpconnect/crashtests/761831.html new file mode 100644 index 000000000..60fdd6d98 --- /dev/null +++ b/js/xpconnect/crashtests/761831.html @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/786142-iframe.html b/js/xpconnect/crashtests/786142-iframe.html new file mode 100644 index 000000000..80c129dab --- /dev/null +++ b/js/xpconnect/crashtests/786142-iframe.html @@ -0,0 +1,84 @@ + + + + + + +
+ + diff --git a/js/xpconnect/crashtests/786142.html b/js/xpconnect/crashtests/786142.html new file mode 100644 index 000000000..d98d39dd3 --- /dev/null +++ b/js/xpconnect/crashtests/786142.html @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/js/xpconnect/crashtests/797583.html b/js/xpconnect/crashtests/797583.html new file mode 100644 index 000000000..f6aaf01c2 --- /dev/null +++ b/js/xpconnect/crashtests/797583.html @@ -0,0 +1,6 @@ + diff --git a/js/xpconnect/crashtests/806751.html b/js/xpconnect/crashtests/806751.html new file mode 100644 index 000000000..4e44b9b82 --- /dev/null +++ b/js/xpconnect/crashtests/806751.html @@ -0,0 +1,26 @@ + + + + + + + + + + diff --git a/js/xpconnect/crashtests/833856.html b/js/xpconnect/crashtests/833856.html new file mode 100644 index 000000000..ca2bfc378 --- /dev/null +++ b/js/xpconnect/crashtests/833856.html @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/851418.html b/js/xpconnect/crashtests/851418.html new file mode 100644 index 000000000..e39ff83e6 --- /dev/null +++ b/js/xpconnect/crashtests/851418.html @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/js/xpconnect/crashtests/854139.html b/js/xpconnect/crashtests/854139.html new file mode 100644 index 000000000..dd3833e9d --- /dev/null +++ b/js/xpconnect/crashtests/854139.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/854604.html b/js/xpconnect/crashtests/854604.html new file mode 100644 index 000000000..41c028bc2 --- /dev/null +++ b/js/xpconnect/crashtests/854604.html @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/js/xpconnect/crashtests/898939.html b/js/xpconnect/crashtests/898939.html new file mode 100644 index 000000000..dd0257283 --- /dev/null +++ b/js/xpconnect/crashtests/898939.html @@ -0,0 +1,18 @@ + + + + + + + + + + diff --git a/js/xpconnect/crashtests/905523.html b/js/xpconnect/crashtests/905523.html new file mode 100644 index 000000000..73ca9fda1 --- /dev/null +++ b/js/xpconnect/crashtests/905523.html @@ -0,0 +1,24904 @@ + + + diff --git a/js/xpconnect/crashtests/938297.html b/js/xpconnect/crashtests/938297.html new file mode 100644 index 000000000..bd2018659 --- /dev/null +++ b/js/xpconnect/crashtests/938297.html @@ -0,0 +1,24 @@ + + + + + + + + + + diff --git a/js/xpconnect/crashtests/crashtests.list b/js/xpconnect/crashtests/crashtests.list new file mode 100644 index 000000000..7325e2601 --- /dev/null +++ b/js/xpconnect/crashtests/crashtests.list @@ -0,0 +1,56 @@ +load 117307-1.html +load 193710.html +load 290162-1.html +load 326615-1.html +load 328553-1.html +load 346258-1.html +load 346512-1.xhtml +load 382133-1.html +load 386680-1.html +load 394810-1.html +load 400349-1.html +load 403356-1.html +load 418139-1.svg +load 420513-1.html +load 453935-1.html +load 462926.html +load 467693-1.html +load 468552-1.html +load 471366-1.html +load 475185-1.html +load 475291-1.html +load 503286-1.html +load 504000-1.html +load 509075-1.html +load 512815-1.html +load 515726-1.html +load 545291-1.html +load 558979.html +load 582649.html +load 601284-1.html +load 603146-1.html +load 603858-1.html +load 608963.html +load 616930-1.html +# This test has jit-related infinite recursion, which is slow enough to cause +# timeouts on mac. See bug 908895. +skip-if(cocoaWidget&&isDebugBuild) load 639737-1.html +load 648206-1.html +load 705875.html +load 720305-1.html +load 723465.html +load 732870.html +load 751995.html +load 761831.html +asserts(0-1) load 752038.html # We may hit bug 645229 here. +asserts(1) load 753162.html # We hit bug 675518 or bug 680086 here. +load 754311.html +asserts(0-1) load 786142.html # We may hit bug 645229 here. +load 797583.html +load 806751.html +load 833856.html +load 851418.html +load 854139.html +load 854604.html +pref(dom.use_xbl_scopes_for_remote_xul,true) load 898939.html +pref(security.fileuri.strict_origin_policy,false) load 938297.html diff --git a/js/xpconnect/idl/moz.build b/js/xpconnect/idl/moz.build new file mode 100644 index 000000000..2438b1a5a --- /dev/null +++ b/js/xpconnect/idl/moz.build @@ -0,0 +1,22 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'mozIJSSubScriptLoader.idl', + 'nsIAddonInterposition.idl', + 'nsIScriptError.idl', + 'nsIXPConnect.idl', + 'nsIXPCScriptable.idl', + 'xpccomponents.idl', + 'xpcexception.idl', + 'xpcIJSGetFactory.idl', + 'xpcIJSModuleLoader.idl', + 'xpcIJSWeakReference.idl', + 'xpcjsid.idl', +] + +XPIDL_MODULE = 'xpconnect' + diff --git a/js/xpconnect/idl/mozIJSSubScriptLoader.idl b/js/xpconnect/idl/mozIJSSubScriptLoader.idl new file mode 100644 index 000000000..1b5ed901b --- /dev/null +++ b/js/xpconnect/idl/mozIJSSubScriptLoader.idl @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; 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" + +interface nsIURI; +interface nsIPrincipal; +interface nsIObserver; + +[scriptable, uuid(19533e7b-f321-4ef1-bc59-6e812dc2a733)] +interface mozIJSSubScriptLoader : nsISupports +{ + /** + * This method should only be called from JS! + * In JS, the signature looks like: + * rv loadSubScript (url [, obj] [, charset]); + * @param url the url of the sub-script, it MUST be either a file:, + * resource:, or chrome: url, and MUST be local. + * @param obj an optional object to evaluate the script onto, it + * defaults to the global object of the caller. + * @param charset optionally specifies the character encoding of + * the file. If absent, the file is interpreted + * as ASCII. + * @retval rv the value returned by the sub-script + */ + [implicit_jscontext] + jsval loadSubScript(in AString url, [optional] in jsval obj, [optional] in AString charset); + + /** + * This method should only be called from JS! + * In JS, the signature looks like: + * rv = loadSubScript (url, optionsObject) + * @param url the url of the sub-script, it MUST be either a file:, + * resource:, or chrome: url, and MUST be local. + * @param optionsObject an object with parameters. Valid parameters are: + * - charset: specifying the character encoding of the file (default: ASCII) + * - target: an object to evaluate onto (default: global object of the caller) + * - ignoreCache: if set to true, will bypass the cache for reading the file. + * - async: if set to true, the script will be loaded + * asynchronously, and a Promise is returned which + * resolves to its result when execution is complete. + * @retval rv the value returned by the sub-script + */ + [implicit_jscontext] + jsval loadSubScriptWithOptions(in AString url, in jsval options); + + /* + * Compiles a JS script off the main thread and calls back the + * observer once it's done. + * The script will be cached in temporary or persistent storage depending + * on the principal used. + * We fire the notification callback in all cases - there is no fatal + * error there. + * @param uri the uri of the script to load. + * @param principal the principal from which we get the app id if any. + * @param observer this observer will be called once the script has + * been precompiled. The notification topic will be + * 'script-precompiled' and the subject the uri of the + * script as a nsIURI. + */ + void precompileScript(in nsIURI uri, + in nsIPrincipal principal, + in nsIObserver observer); +}; diff --git a/js/xpconnect/idl/nsIAddonInterposition.idl b/js/xpconnect/idl/nsIAddonInterposition.idl new file mode 100644 index 000000000..1d1166ef2 --- /dev/null +++ b/js/xpconnect/idl/nsIAddonInterposition.idl @@ -0,0 +1,67 @@ +/* -*- 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" + +/** + * This interface allows Firefox to expose different implementations of its own + * classes to add-ons. Once an interposition is created, it must be assigned to + * an add-on using Cu.setAddonInterposition (JS) or xpc::SetAddonInterposition + * (C++). In both cases, the arguments should be the add-on ID and the + * interposition object (which must be an nsIAddonInterposition). This must + * happen before any compartments are created for the given add-on. + * + * Every time the add-on accesses a property on any object outside its own set + * of compartments, XPConnect will call the interposition's + * interpose method. If the interposition wants to replace the given + * property, it should return a replacement property descriptor for it. If not, + * it should return null. + */ +[scriptable,uuid(d05cc5fd-ad88-41a6-854c-36fd94d69ddb)] +interface nsIAddonInterposition : nsISupports +{ + /** + * Returns a replacement property descriptor for a browser object. + * + * @param addonId The ID of the add-on accessing the property. + * @param target The browser object being accessed. + * @param iface The IID of the interface the property is associated with. This + * parameter is only available for XPCWrappedNative targets. As + * such, it's only useful as an optimization to avoid + * instanceof checks on the target. + * @param prop The name of the property being accessed. + * @return A property descriptor or null. + */ + jsval interposeProperty(in jsval addonId, + in jsval target, + in nsIIDPtr iface, + in jsval prop); + /** + * We're intercepting calls from add-ons scopes into non-addon scopes. + * + * @param addonId The ID of the add-on accessing the property. + * @param originalFunc The function object being intercepted. + * @param originalThis The |this| value for the intercepted call. + * @param args The arguments of the original call in an array. + * @return The result of the call. NOTE: after the call interception, + * the original function will not be called automatically, so the + * implementer has to do that. + */ + jsval interposeCall(in jsval addonId, + in jsval originalFunc, + in jsval originalThis, + in jsval args); + + /** + * For the first time when the interposition is registered the engine + * calls getWhitelist and expects an array of strings. The strings are + * the name of properties the interposition wants interposeProperty + * to be called. It can be an empty array. + * Note: for CPOWs interposeProperty is always called regardless if + * the name of the property is on the whitelist or not. + */ + jsval getWhitelist(); +}; diff --git a/js/xpconnect/idl/nsIScriptError.idl b/js/xpconnect/idl/nsIScriptError.idl new file mode 100644 index 000000000..468ca682f --- /dev/null +++ b/js/xpconnect/idl/nsIScriptError.idl @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; 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/. */ + +/* + * nsIConsoleMessage subclass for representing JavaScript errors and warnings. + */ + + +#include "nsISupports.idl" +#include "nsIConsoleMessage.idl" + +%{C++ +#include "nsStringGlue.h" // for nsDependentCString +%} + +[scriptable, uuid(361be358-76f0-47aa-b37b-6ad833599e8d)] +interface nsIScriptError : nsIConsoleMessage +{ + /** pseudo-flag for default case */ + const unsigned long errorFlag = 0x0; + + /** message is warning */ + const unsigned long warningFlag = 0x1; + + /** exception was thrown for this case - exception-aware hosts can ignore */ + const unsigned long exceptionFlag = 0x2; + + // XXX check how strict is implemented these days. + /** error or warning is due to strict option */ + const unsigned long strictFlag = 0x4; + + /** just a log message */ + const unsigned long infoFlag = 0x8; + + /** + * The error message without any context/line number information. + * + * @note nsIConsoleMessage.message will return the error formatted + * with file/line information. + */ + readonly attribute AString errorMessage; + + readonly attribute AString sourceName; + readonly attribute AString sourceLine; + readonly attribute uint32_t lineNumber; + readonly attribute uint32_t columnNumber; + readonly attribute uint32_t flags; + + /** + * Categories I know about - + * XUL javascript + * content javascript (both of these from nsDocShell, currently) + * system javascript (errors in JS components and other system JS) + */ + readonly attribute string category; + + /* Get the window id this was initialized with. Zero will be + returned if init() was used instead of initWithWindowID(). */ + readonly attribute unsigned long long outerWindowID; + + /* Get the inner window id this was initialized with. Zero will be + returned if init() was used instead of initWithWindowID(). */ + readonly attribute unsigned long long innerWindowID; + + readonly attribute boolean isFromPrivateWindow; + + attribute jsval stack; + + /** + * The name of a template string, as found in js.msg, associated with the + * error message. + */ + attribute AString errorMessageName; + + + void init(in AString message, + in AString sourceName, + in AString sourceLine, + in uint32_t lineNumber, + in uint32_t columnNumber, + in uint32_t flags, + in string category); + + /* This should be called instead of nsIScriptError.init to + initialize with a window id. The window id should be for the + inner window associated with this error. */ + void initWithWindowID(in AString message, + in AString sourceName, + in AString sourceLine, + in uint32_t lineNumber, + in uint32_t columnNumber, + in uint32_t flags, + in ACString category, + in unsigned long long innerWindowID); +%{C++ + // This overload allows passing a literal string for category. + template + nsresult InitWithWindowID(const nsAString& message, + const nsAString& sourceName, + const nsAString& sourceLine, + uint32_t lineNumber, + uint32_t columnNumber, + uint32_t flags, + const char (&c)[N], + uint64_t aInnerWindowID) + { + nsDependentCString category(c, N - 1); + return InitWithWindowID(message, sourceName, sourceLine, lineNumber, + columnNumber, flags, category, aInnerWindowID); + } +%} + +}; + +%{ C++ +#define NS_SCRIPTERROR_CID \ +{ 0x1950539a, 0x90f0, 0x4d22, { 0xb5, 0xaf, 0x71, 0x32, 0x9c, 0x68, 0xfa, 0x35 }} + +#define NS_SCRIPTERROR_CONTRACTID "@mozilla.org/scripterror;1" +%} diff --git a/js/xpconnect/idl/nsIXPCScriptable.idl b/js/xpconnect/idl/nsIXPCScriptable.idl new file mode 100644 index 000000000..b7ba02b8a --- /dev/null +++ b/js/xpconnect/idl/nsIXPCScriptable.idl @@ -0,0 +1,157 @@ +/* -*- 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" +#include "nsIClassInfo.idl" + +%{C++ +#ifdef XP_WIN +#undef GetClassName +#endif + +#include "js/TypeDecls.h" + +struct JSFreeOp; +namespace js { +struct Class; +} +%} + +interface nsIXPConnectWrappedNative; + +[ptr] native JSContextPtr(JSContext); +[ptr] native JSObjectPtr(JSObject); +[ptr] native JSValPtr(JS::Value); +[ptr] native JSFreeOpPtr(JSFreeOp); +[ref] native JSCallArgsRef(const JS::CallArgs); +[ref] native JSAutoIdVector(JS::AutoIdVector); +[ptr] native jsClassPtr(const js::Class); + +/** + * Note: This is not really an XPCOM interface. For example, callers must + * guarantee that they set the *_retval of the various methods that return a + * boolean to PR_TRUE before making the call. Implementations may skip writing + * to *_retval unless they want to return PR_FALSE. + */ +[uuid(19b70b26-7c3f-437f-a04a-2a8f9e28b617)] +interface nsIXPCScriptable : nsISupports +{ + /* bitflags used for 'flags' (only 32 bits available!) */ + + const uint32_t WANT_PRECREATE = 1 << 0; + // unused bit here + // unused bit here + const uint32_t WANT_ADDPROPERTY = 1 << 3; + // unused bit here + const uint32_t WANT_GETPROPERTY = 1 << 5; + const uint32_t WANT_SETPROPERTY = 1 << 6; + const uint32_t WANT_ENUMERATE = 1 << 7; + const uint32_t WANT_NEWENUMERATE = 1 << 8; + const uint32_t WANT_RESOLVE = 1 << 9; + // unused bit here + const uint32_t WANT_FINALIZE = 1 << 11; + // unused bit here! + const uint32_t WANT_CALL = 1 << 13; + const uint32_t WANT_CONSTRUCT = 1 << 14; + const uint32_t WANT_HASINSTANCE = 1 << 15; + // Unused bit here! + const uint32_t USE_JSSTUB_FOR_ADDPROPERTY = 1 << 17; + const uint32_t USE_JSSTUB_FOR_DELPROPERTY = 1 << 18; + const uint32_t USE_JSSTUB_FOR_SETPROPERTY = 1 << 19; + // Unused bit here! + const uint32_t DONT_ENUM_QUERY_INTERFACE = 1 << 21; + const uint32_t DONT_ASK_INSTANCE_FOR_SCRIPTABLE = 1 << 22; + const uint32_t CLASSINFO_INTERFACES_ONLY = 1 << 23; + const uint32_t ALLOW_PROP_MODS_DURING_RESOLVE = 1 << 24; + const uint32_t ALLOW_PROP_MODS_TO_PROTOTYPE = 1 << 25; + const uint32_t IS_GLOBAL_OBJECT = 1 << 26; + const uint32_t DONT_REFLECT_INTERFACE_NAMES = 1 << 27; + + // The high order bit is RESERVED for consumers of these flags. + // No implementor of this interface should ever return flags + // with this bit set. + const uint32_t RESERVED = 1 << 31; + + readonly attribute string className; + [notxpcom,nostdcall] uint32_t getScriptableFlags(); + [notxpcom,nostdcall] jsClassPtr getClass(); + + void preCreate(in nsISupports nativeObj, in JSContextPtr cx, + in JSObjectPtr globalObj, out JSObjectPtr parentObj); + + boolean addProperty(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, in jsid id, + in jsval val); + + boolean getProperty(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, in jsid id, + in JSValPtr vp); + + boolean setProperty(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, in jsid id, + in JSValPtr vp); + + boolean enumerate(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj); + + boolean newEnumerate(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, + in JSAutoIdVector properties); + + boolean resolve(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, in jsid id, + out boolean resolvedp); + + void finalize(in nsIXPConnectWrappedNative wrapper, + in JSFreeOpPtr fop, in JSObjectPtr obj); + + boolean call(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, + in JSCallArgsRef args); + + boolean construct(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, + in JSCallArgsRef args); + + boolean hasInstance(in nsIXPConnectWrappedNative wrapper, + in JSContextPtr cx, in JSObjectPtr obj, + in jsval val, out boolean bp); + + void postCreatePrototype(in JSContextPtr cx, in JSObjectPtr proto); +}; + +%{ C++ + +#include "nsAutoPtr.h" + +#define NS_XPCCLASSINFO_IID \ +{ 0x43b67f01, 0xd4ce, 0x4b82, \ + { 0xb3, 0xf8, 0xeb, 0xf2, 0x13, 0x60, 0xfb, 0x7e } } + +class NS_NO_VTABLE nsXPCClassInfo : public nsIClassInfo, + public nsIXPCScriptable +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_XPCCLASSINFO_IID) + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override = 0; + NS_IMETHOD_(MozExternalRefCountType) Release() override = 0; + + virtual void PreserveWrapper(nsISupports *aNative) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsXPCClassInfo, NS_XPCCLASSINFO_IID) + +inline +nsresult +CallQueryInterface(nsISupports* aSourcePtr, + RefPtrGetterAddRefs aDestPtr) +{ + return CallQueryInterface(aSourcePtr, + static_cast(aDestPtr)); +} + +%} diff --git a/js/xpconnect/idl/nsIXPConnect.idl b/js/xpconnect/idl/nsIXPConnect.idl new file mode 100644 index 000000000..4bdc007fc --- /dev/null +++ b/js/xpconnect/idl/nsIXPConnect.idl @@ -0,0 +1,542 @@ +/* -*- Mode: IDL; 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/. */ + +/* The core XPConnect public interfaces. */ + +#include "nsISupports.idl" + +%{ C++ +#include "jspubtd.h" +#include "js/TypeDecls.h" +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" + +struct JSFreeOp; + +class nsWrapperCache; +class nsAXPCNativeCallContext; +%} + +/***************************************************************************/ + +// NB: jsval and jsid are declared in nsrootidl.idl + +[ptr] native JSContextPtr(JSContext); +[ptr] native JSClassPtr(JSClass); +[ptr] native JSFreeOpPtr(JSFreeOp); +[ptr] native JSObjectPtr(JSObject); +[ptr] native JSValConstPtr(const JS::Value); + native JSEqualityOp(JSEqualityOp); +[ptr] native JSScriptPtr(JSScript); +[ptr] native voidPtrPtr(void*); +[ptr] native nsAXPCNativeCallContextPtr(nsAXPCNativeCallContext); +[ptr] native nsWrapperCachePtr(nsWrapperCache); +[ref] native JSCompartmentOptions(JS::CompartmentOptions); +[ref] native JSCallArgsRef(const JS::CallArgs); + native JSHandleId(JS::Handle); + +/***************************************************************************/ + +// forward declarations... +interface nsIXPCScriptable; +interface nsIXPConnect; +interface nsIXPConnectWrappedNative; +interface nsIInterfaceInfo; +interface nsIXPCSecurityManager; +interface nsIPrincipal; +interface nsIClassInfo; +interface nsIVariant; +interface nsIStackFrame; +interface nsIObjectInputStream; +interface nsIObjectOutputStream; + +/***************************************************************************/ +[uuid(73e6ff4a-ab99-4d99-ac00-ba39ccb8e4d7)] +interface nsIXPConnectJSObjectHolder : nsISupports +{ + [notxpcom, nostdcall] JSObjectPtr GetJSObject(); +}; + +[uuid(e787be29-db5d-4a45-a3d6-1de1d6b85c30)] +interface nsIXPConnectWrappedNative : nsIXPConnectJSObjectHolder +{ + /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */ + readonly attribute nsISupports Native; + readonly attribute JSObjectPtr JSObjectPrototype; + + /** + * These are here as an aid to nsIXPCScriptable implementors + */ + + nsIInterfaceInfo FindInterfaceWithMember(in JSHandleId nameID); + nsIInterfaceInfo FindInterfaceWithName(in JSHandleId nameID); + [notxpcom] bool HasNativeMember(in JSHandleId name); + + void debugDump(in short depth); + + /* + * NOTE: Add new IDL methods _before_ the C++ block below if you + * add them. Otherwise the vtable won't be what xpidl thinks it + * is, since GetObjectPrincipal() is virtual. + */ + +%{C++ + /** + * Faster access to the native object from C++. Will never return null. + */ + nsISupports* Native() const { return mIdentity; } + +protected: + nsCOMPtr mIdentity; +public: +%} +}; + +%{C++ + +inline +const nsQueryInterface +do_QueryWrappedNative(nsIXPConnectWrappedNative *aWrappedNative) +{ + return nsQueryInterface(aWrappedNative->Native()); +} + +inline +const nsQueryInterfaceWithError +do_QueryWrappedNative(nsIXPConnectWrappedNative *aWrappedNative, + nsresult *aError) + +{ + return nsQueryInterfaceWithError(aWrappedNative->Native(), aError); +} + +%} + +[uuid(3a01b0d6-074b-49ed-bac3-08c76366cae4)] +interface nsIXPConnectWrappedJS : nsIXPConnectJSObjectHolder +{ + /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */ + readonly attribute nsIInterfaceInfo InterfaceInfo; + readonly attribute nsIIDPtr InterfaceIID; + + void debugDump(in short depth); + + void aggregatedQueryInterface(in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); + +}; + +// Special interface to unmark the internal JSObject. +// QIing to nsIXPConnectWrappedJSUnmarkGray does *not* addref, it only unmarks, +// and QIing to nsIXPConnectWrappedJSUnmarkGray is always supposed to fail. +[builtinclass, uuid(c02a0ce6-275f-4ea1-9c23-08494898b070)] +interface nsIXPConnectWrappedJSUnmarkGray : nsIXPConnectWrappedJS +{ +}; + +/***************************************************************************/ + +/** + * This is a sort of a placeholder interface. It is not intended to be + * implemented. It exists to give the nsIXPCSecurityManager an iid on + * which to gate a specific activity in XPConnect. + * + * That activity is... + * + * When JavaScript code uses a component that is itself implemented in + * JavaScript then XPConnect will build a wrapper rather than directly + * expose the JSObject of the component. This allows components implemented + * in JavaScript to 'look' just like any other xpcom component (from the + * perspective of the JavaScript caller). This insulates the component from + * the caller and hides any properties or methods that are not part of the + * interface as declared in xpidl. Usually this is a good thing. + * + * However, in some cases it is useful to allow the JS caller access to the + * JS component's underlying implementation. In order to facilitate this + * XPConnect supports the 'wrappedJSObject' property. The caller code can do: + * + * // 'foo' is some xpcom component (that might be implemented in JS). + * try { + * var bar = foo.wrappedJSObject; + * if(bar) { + * // bar is the underlying JSObject. Do stuff with it here. + * } + * } catch(e) { + * // security exception? + * } + * + * Recall that 'foo' above is an XPConnect wrapper, not the underlying JS + * object. The property get "foo.wrappedJSObject" will only succeed if three + * conditions are met: + * + * 1) 'foo' really is an XPConnect wrapper around a JSObject. + * 2) The underlying JSObject actually implements a "wrappedJSObject" + * property that returns a JSObject. This is called by XPConnect. This + * restriction allows wrapped objects to only allow access to the underlying + * JSObject if they choose to do so. Ususally this just means that 'foo' + * would have a property tht looks like: + * this.wrappedJSObject = this. + * 3) The implemementation of nsIXPCSecurityManager (if installed) allows + * a property get on the interface below. Although the JSObject need not + * implement 'nsIXPCWrappedJSObjectGetter', XPConnect will ask the + * security manager if it is OK for the caller to access the only method + * in nsIXPCWrappedJSObjectGetter before allowing the activity. This fits + * in with the security manager paradigm and makes control over accessing + * the property on this interface the control factor for getting the + * underlying wrapped JSObject of a JS component from JS code. + * + * Notes: + * + * a) If 'foo' above were the underlying JSObject and not a wrapper at all, + * then this all just works and XPConnect is not part of the picture at all. + * b) One might ask why 'foo' should not just implement an interface through + * which callers might get at the underlying object. There are three reasons: + * i) XPConnect would still have to do magic since JSObject is not a + * scriptable type. + * ii) JS Components might use aggregation (like C++ objects) and have + * different JSObjects for different interfaces 'within' an aggregate + * object. But, using an additional interface only allows returning one + * underlying JSObject. However, this allows for the possibility that + * each of the aggregte JSObjects could return something different. + * Note that one might do: this.wrappedJSObject = someOtherObject; + * iii) Avoiding the explicit interface makes it easier for both the caller + * and the component. + * + * Anyway, some future implementation of nsIXPCSecurityManager might want + * do special processing on 'nsIXPCSecurityManager::CanGetProperty' when + * the interface id is that of nsIXPCWrappedJSObjectGetter. + */ + +[scriptable, uuid(254bb2e0-6439-11d4-8fe0-0010a4e73d9a)] +interface nsIXPCWrappedJSObjectGetter : nsISupports +{ + readonly attribute nsISupports neverCalled; +}; + +/***************************************************************************/ + +/* + * This interface is implemented by outside code and registered with xpconnect + * via nsIXPConnect::setFunctionThisTranslator. + * + * The reason this exists is to support calls to JavaScript event callbacks + * needed by the DOM via xpconnect from C++ code. + * + * We've added support for wrapping JS function objects as xpcom interfaces + * by declaring the given interface as a [function] interface. However, to + * support the requirements of JS event callbacks we need to call the JS + * function with the 'this' set as the JSObject for which the event is being + * fired; e.g. a form node. + * + * We've decided that for all cases we care about the appropriate 'this' object + * can be derived from the first param in the call to the callback. In the + * event handler case the first param is an event object. + * + * Though we can't change all the JS code so that it would setup its own 'this', + * we can add plugin 'helper' support to xpconnect. And that is what we have + * here. + * + * The idea is that at startup time some code that cares about this issue + * (e.g. the DOM helper code) can register a nsIXPCFunctionThisTranslator + * object with xpconnect to handle calls to [function] interfaces of a given + * iid. When xpconnect goes to invoke a method on a wrapped JSObject for + * an interface marked as [function], xpconnect will check if the first param + * of the method is an xpcom object pointer and if so it will check to see if a + * nsIXPCFunctionThisTranslator has been registered for the given iid of the + * interface being called. If so it will call the translator and get an + * interface pointer to use as the 'this' for the call. If the translator + * returns a non-null interface pointer (which it should then have addref'd + * since it is being returned as an out param), xpconnect will attempt to build + * a wrapper around the pointer and get a JSObject from that wrapper to use + * as the 'this' for the call. + * + * If a null interface pointer is returned then xpconnect will use the default + * 'this' - the same JSObject as the function object it is calling. + */ + +[uuid(f5f84b70-92eb-41f1-a1dd-2eaac0ed564c)] +interface nsIXPCFunctionThisTranslator : nsISupports +{ + nsISupports TranslateThis(in nsISupports aInitialThis); +}; + +/***************************************************************************/ + + +%{ C++ +// For use with the service manager +// {CB6593E0-F9B2-11d2-BDD6-000064657374} +#define NS_XPCONNECT_CID \ +{ 0xcb6593e0, 0xf9b2, 0x11d2, \ + { 0xbd, 0xd6, 0x0, 0x0, 0x64, 0x65, 0x73, 0x74 } } +%} + +[noscript, uuid(768507b5-b981-40c7-8276-f6a1da502a24)] +interface nsIXPConnect : nsISupports +{ +%{ C++ + NS_DEFINE_STATIC_CID_ACCESSOR(NS_XPCONNECT_CID) +%} + + /** + * Creates a new global object using the given aCOMObj as the global + * object. The object will be set up according to the flags (defined + * below). If you do not pass INIT_JS_STANDARD_CLASSES, then aCOMObj + * must implement nsIXPCScriptable so it can resolve the standard + * classes when asked by the JS engine. + * + * @param aJSContext the context to use while creating the global object. + * @param aCOMObj the native object that represents the global object. + * @param aPrincipal the principal of the code that will run in this + * compartment. Can be null if not on the main thread. + * @param aFlags one of the flags below specifying what options this + * global object wants. + * @param aOptions JSAPI-specific options for the new compartment. + */ + nsIXPConnectJSObjectHolder + initClassesWithNewWrappedGlobal( + in JSContextPtr aJSContext, + in nsISupports aCOMObj, + in nsIPrincipal aPrincipal, + in uint32_t aFlags, + in JSCompartmentOptions aOptions); + + const uint32_t INIT_JS_STANDARD_CLASSES = 1 << 0; + const uint32_t DONT_FIRE_ONNEWGLOBALHOOK = 1 << 1; + const uint32_t OMIT_COMPONENTS_OBJECT = 1 << 2; + + /** + * wrapNative will create a new JSObject or return an existing one. + * + * This method now correctly deals with cases where the passed in xpcom + * object already has an associated JSObject for the cases: + * 1) The xpcom object has already been wrapped for use in the same scope + * as an nsIXPConnectWrappedNative. + * 2) The xpcom object is in fact a nsIXPConnectWrappedJS and thus already + * has an underlying JSObject. + * + * It *might* be possible to QueryInterface the nsIXPConnectJSObjectHolder + * returned by the method into a nsIXPConnectWrappedNative or a + * nsIXPConnectWrappedJS. + * + * This method will never wrap the JSObject involved in an + * XPCNativeWrapper before returning. + * + * Returns: + * success: + * NS_OK + * failure: + * NS_ERROR_XPC_BAD_CONVERT_NATIVE + * NS_ERROR_XPC_CANT_GET_JSOBJECT_OF_DOM_OBJECT + * NS_ERROR_FAILURE + */ + JSObjectPtr + wrapNative(in JSContextPtr aJSContext, + in JSObjectPtr aScope, + in nsISupports aCOMObj, + in nsIIDRef aIID); + + /** + * Same as wrapNative, but it returns the JSObject in an nsIXPConnectJSObjectHolder. + */ + nsIXPConnectJSObjectHolder + wrapNativeHolder(in JSContextPtr aJSContext, + in JSObjectPtr aScope, + in nsISupports aCOMObj, + in nsIIDRef aIID); + + /** + * Same as wrapNative, but it returns the JSObject in aVal. C++ callers + * must ensure that aVal is rooted. + * aIID may be null, it means the same as passing in + * &NS_GET_IID(nsISupports) but when passing in null certain shortcuts + * can be taken because we know without comparing IIDs that the caller is + * asking for an nsISupports wrapper. + * If aAllowWrapper, then the returned value will be wrapped in the proper + * type of security wrapper on top of the XPCWrappedNative (if needed). + * This method doesn't push aJSContext on the context stack, so the caller + * is required to push it if the top of the context stack is not equal to + * aJSContext. + */ + void + wrapNativeToJSVal(in JSContextPtr aJSContext, + in JSObjectPtr aScope, + in nsISupports aCOMObj, + in nsWrapperCachePtr aCache, + in nsIIDPtr aIID, + in boolean aAllowWrapper, + out jsval aVal); + + /** + * wrapJS will yield a new or previously existing xpcom interface pointer + * to represent the JSObject passed in. + * + * This method now correctly deals with cases where the passed in JSObject + * already has an associated xpcom interface for the cases: + * 1) The JSObject has already been wrapped as a nsIXPConnectWrappedJS. + * 2) The JSObject is in fact a nsIXPConnectWrappedNative and thus already + * has an underlying xpcom object. + * 3) The JSObject is of a jsclass which supports getting the nsISupports + * from the JSObject directly. This is used for idlc style objects + * (e.g. DOM objects). + * + * It *might* be possible to QueryInterface the resulting interface pointer + * to nsIXPConnectWrappedJS. + * + * Returns: + * success: + * NS_OK + * failure: + * NS_ERROR_XPC_BAD_CONVERT_JS + * NS_ERROR_FAILURE + */ + void + wrapJS(in JSContextPtr aJSContext, + in JSObjectPtr aJSObj, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * Wraps the given jsval in a nsIVariant and returns the new variant. + */ + nsIVariant + jSValToVariant(in JSContextPtr cx, in jsval aJSVal); + + /** + * This only succeeds if the JSObject is a nsIXPConnectWrappedNative. + * A new wrapper is *never* constructed. + */ + nsIXPConnectWrappedNative + getWrappedNativeOfJSObject(in JSContextPtr aJSContext, + in JSObjectPtr aJSObj); + + // Will return null if there is no JS stack right now. + readonly attribute nsIStackFrame CurrentJSStack; + readonly attribute nsAXPCNativeCallContextPtr CurrentNativeCallContext; + + void debugDump(in short depth); + void debugDumpObject(in nsISupports aCOMObj, in short depth); + void debugDumpJSStack(in boolean showArgs, + in boolean showLocals, + in boolean showThisProps); + + /** + * wrapJSAggregatedToNative is just like wrapJS except it is used in cases + * where the JSObject is also aggregated to some native xpcom Object. + * At present XBL is the only system that might want to do this. + * + * XXX write more! + * + * Returns: + * success: + * NS_OK + * failure: + * NS_ERROR_XPC_BAD_CONVERT_JS + * NS_ERROR_FAILURE + */ + void + wrapJSAggregatedToNative(in nsISupports aOuter, + in JSContextPtr aJSContext, + in JSObjectPtr aJSObj, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + // Methods added since mozilla 0.6.... + + /** + * This only succeeds if the native object is already wrapped by xpconnect. + * A new wrapper is *never* constructed. + */ + nsIXPConnectWrappedNative + getWrappedNativeOfNativeObject(in JSContextPtr aJSContext, + in JSObjectPtr aScope, + in nsISupports aCOMObj, + in nsIIDRef aIID); + + void + setFunctionThisTranslator(in nsIIDRef aIID, + in nsIXPCFunctionThisTranslator aTranslator); + + JSObjectPtr + getWrappedNativePrototype(in JSContextPtr aJSContext, + in JSObjectPtr aScope, + in nsIClassInfo aClassInfo); + + jsval variantToJS(in JSContextPtr ctx, in JSObjectPtr scope, in nsIVariant value); + nsIVariant JSToVariant(in JSContextPtr ctx, in jsval value); + + /** + * Create a sandbox for evaluating code in isolation using + * evalInSandboxObject(). + * + * @param cx A context to use when creating the sandbox object. + * @param principal The principal (or NULL to use the null principal) + * to use when evaluating code in this sandbox. + */ + [noscript] JSObjectPtr createSandbox(in JSContextPtr cx, in nsIPrincipal principal); + + /** + * Evaluate script in a sandbox, completely isolated from all + * other running scripts. + * + * @param source The source of the script to evaluate. + * @param filename The filename of the script. May be null. + * @param cx The context to use when setting up the evaluation of + * the script. The actual evaluation will happen on a new + * temporary context. + * @param sandbox The sandbox object to evaluate the script in. + * @param version The JavaScript version to use for evaluating the script. + * Should be a valid JSVersion from jspubtd.h. + * @return The result of the evaluation as a jsval. If the caller + * intends to use the return value from this call the caller + * is responsible for rooting the jsval before making a call + * to this method. + */ + [noscript] jsval evalInSandboxObject(in AString source, in string filename, + in JSContextPtr cx, + in JSObjectPtr sandbox, + in int32_t version); + + /** + * Trigger a JS garbage collection. + * Use a js::gcreason::Reason from jsfriendapi.h for the kind. + */ + void GarbageCollect(in uint32_t reason); + + /** + * Signals a good place to do an incremental GC slice, because the + * browser is drawing a frame. + */ + void NotifyDidPaint(); + +%{C++ + /** + * Get the object principal for this wrapper. Note that this may well end + * up being null; in that case one should seek principals elsewhere. Null + * here does NOT indicate system principal or no principals at all, just + * that this wrapper doesn't have an intrinsic one. + */ + virtual nsIPrincipal* GetPrincipal(JSObject* obj, + bool allowShortCircuit) const = 0; + virtual char* DebugPrintJSStack(bool showArgs, + bool showLocals, + bool showThisProps) = 0; +%} + + [noscript] void writeScript(in nsIObjectOutputStream aStream, + in JSContextPtr aJSContext, + in JSScriptPtr aJSScript); + + [noscript] JSScriptPtr readScript(in nsIObjectInputStream aStream, + in JSContextPtr aJSContext); + + [noscript] void writeFunction(in nsIObjectOutputStream aStream, + in JSContextPtr aJSContext, + in JSObjectPtr aJSObject); + + [noscript] JSObjectPtr readFunction(in nsIObjectInputStream aStream, + in JSContextPtr aJSContext); +}; diff --git a/js/xpconnect/idl/xpcIJSGetFactory.idl b/js/xpconnect/idl/xpcIJSGetFactory.idl new file mode 100644 index 000000000..a8ba5524d --- /dev/null +++ b/js/xpconnect/idl/xpcIJSGetFactory.idl @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIFactory; + +/** + * Every JS module exports a single NSGetFactory symbol which is converted into this + * functional interface type. + */ +[scriptable, function, uuid(3FE0C205-D75B-4CAC-9347-D2B855050143)] +interface xpcIJSGetFactory : nsISupports +{ + nsIFactory get(in nsCIDRef aCID); +}; diff --git a/js/xpconnect/idl/xpcIJSModuleLoader.idl b/js/xpconnect/idl/xpcIJSModuleLoader.idl new file mode 100644 index 000000000..5f66b105a --- /dev/null +++ b/js/xpconnect/idl/xpcIJSModuleLoader.idl @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[ptr] native nsAXPCNativeCallContextPtr(nsAXPCNativeCallContext); + +%{C++ +#include "js/TypeDecls.h" + +class nsAXPCNativeCallContext; +%} + +[ptr] native JSObjectPtr(JSObject); + +[scriptable, uuid(4f94b21f-2920-4bd9-8251-5fb60fb054b2)] +interface xpcIJSModuleLoader : nsISupports +{ + /** + * To be called from JavaScript only. + * + * Synchronously loads and evaluates the js file located at + * aResourceURI with a new, fully privileged global object. + * + * If 'targetObj' is specified and equal to null, returns the + * module's global object. Otherwise (if 'targetObj' is not + * specified, or 'targetObj' is != null) looks for a property + * 'EXPORTED_SYMBOLS' on the new global object. 'EXPORTED_SYMBOLS' + * is expected to be an array of strings identifying properties on + * the global object. These properties will be installed as + * properties on 'targetObj', or, if 'targetObj' is not specified, + * on the caller's global object. If 'EXPORTED_SYMBOLS' is not + * found, an error is thrown. + * + * @param resourceURI A resource:// URI string to load the module from. + * @param targetObj the object to install the exported properties on. + * If this parameter is a primitive value, this method throws + * an exception. + * @returns the module code's global object. + * + * The implementation maintains a hash of registryLocation->global obj. + * Subsequent invocations of importModule with 'registryLocation' + * pointing to the same file will not cause the module to be re-evaluated, + * but the symbols in EXPORTED_SYMBOLS will be exported into the + * specified target object and the global object returned as above. + * + * (This comment is duplicated to nsIXPCComponents_Utils.) + */ + [implicit_jscontext,optional_argc] + jsval import(in AUTF8String aResourceURI, [optional] in jsval targetObj); + + /** + * Imports the JS module at aResourceURI to the JS object + * 'targetObj' (if != null) as described for importModule() and + * returns the module's global object. + */ + [noscript] JSObjectPtr importInto(in AUTF8String aResourceURI, + in JSObjectPtr targetObj, + in nsAXPCNativeCallContextPtr cc); + + /** + * Unloads the JS module at aResourceURI. Existing references to the module + * will continue to work but any subsequent import of the module will + * reload it and give new reference. If the JS module hasn't yet been imported + * then this method will do nothing. + */ + void unload(in AUTF8String aResourceURI); + + /** + * Returns true if the js file located at 'registryLocation' location has + * been loaded previously via the import method above. Returns false + * otherwise. + * + * @param resourceURI A resource:// URI string representing the location of + * the js file to be checked if it is already loaded or not. + * @returns boolean, true if the js file has been loaded via import. false + * otherwise + */ + boolean isModuleLoaded(in AUTF8String aResourceURI); +}; diff --git a/js/xpconnect/idl/xpcIJSWeakReference.idl b/js/xpconnect/idl/xpcIJSWeakReference.idl new file mode 100644 index 000000000..4c8b40b3d --- /dev/null +++ b/js/xpconnect/idl/xpcIJSWeakReference.idl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(75767928-ecb1-4e6c-9f55-c118b297fcef)] +interface xpcIJSWeakReference : nsISupports +{ + /** + * To be called from JS only. + * + * Returns the referenced JS object or null if the JS object has + * been garbage collected. + */ + [implicit_jscontext] jsval get(); +}; diff --git a/js/xpconnect/idl/xpccomponents.idl b/js/xpconnect/idl/xpccomponents.idl new file mode 100644 index 000000000..711ea4c64 --- /dev/null +++ b/js/xpconnect/idl/xpccomponents.idl @@ -0,0 +1,732 @@ +/* -*- Mode: IDL; 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" + +%{C++ +#include "jspubtd.h" +%} + +interface xpcIJSWeakReference; +interface nsIAddonInterposition; +interface nsIClassInfo; +interface nsIComponentManager; +interface nsICycleCollectorListener; +interface nsIJSCID; +interface nsIJSIID; +interface nsIPrincipal; +interface nsIStackFrame; + +/** +* interface of Components.interfacesByID +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(f235ef76-9919-478b-aa0f-282d994ddf76)] +interface nsIXPCComponents_InterfacesByID : nsISupports +{ +}; + +/** +* interface of Components.interfaces +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(b8c31bba-79db-4a1d-930d-4cdd68713f9e)] +interface nsIXPCComponents_Interfaces : nsISupports +{ +}; + +/** +* interface of Components.classes +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(978ff520-d26c-11d2-9842-006008962422)] +interface nsIXPCComponents_Classes : nsISupports +{ +}; + +/** +* interface of Components.classesByID +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(336a9590-4d19-11d3-9893-006008962422)] +interface nsIXPCComponents_ClassesByID : nsISupports +{ +}; + +/** +* interface of Components.results +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(2fc229a0-5860-11d3-9899-006008962422)] +interface nsIXPCComponents_Results : nsISupports +{ +}; + +/** +* interface of Components.ID +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(7994a6e0-e028-11d3-8f5d-0010a4e73d9a)] +interface nsIXPCComponents_ID : nsISupports +{ +}; + +/** +* interface of Components.Exception +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(5bf039c0-e028-11d3-8f5d-0010a4e73d9a)] +interface nsIXPCComponents_Exception : nsISupports +{ +}; + +/** +* interface of Components.Constructor +* (interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(88655640-e028-11d3-8f5d-0010a4e73d9a)] +interface nsIXPCComponents_Constructor : nsISupports +{ +}; + +/** +* interface of object returned by Components.Constructor +* (additional interesting stuff only reflected into JavaScript) +*/ +[scriptable, uuid(c814ca20-e0dc-11d3-8f5f-0010a4e73d9a)] +interface nsIXPCConstructor : nsISupports +{ + readonly attribute nsIJSCID classID; + readonly attribute nsIJSIID interfaceID; + readonly attribute string initializer; +}; + +/** +* interface of object returned by Components.utils.Sandbox. +*/ +[scriptable, uuid(4f8ae0dc-d266-4a32-875b-6a9de71a8ce9)] +interface nsIXPCComponents_utils_Sandbox : nsISupports +{ +}; + +/** + * interface for callback to be passed to Cu.schedulePreciseGC + */ +[scriptable, function, uuid(71000535-b0fd-44d1-8ce0-909760e3953c)] +interface ScheduledGCCallback : nsISupports +{ + void callback(); +}; + +/** +* interface of Components.utils +*/ +[scriptable, uuid(86003fe3-ee9a-4620-91dc-eef8b1e58815)] +interface nsIXPCComponents_Utils : nsISupports +{ + + /* reportError is designed to be called from JavaScript only. + * + * It will report a JS Error object to the JS console, and return. It + * is meant for use in exception handler blocks which want to "eat" + * an exception, but still want to report it to the console. + * + * It must be called with one param, usually an object which was caught by + * an exception handler. If it is not a JS error object, the parameter + * is converted to a string and reported as a new error. + */ + [implicit_jscontext] void reportError(in jsval error); + + readonly attribute nsIXPCComponents_utils_Sandbox Sandbox; + + /* + * evalInSandbox is designed to be called from JavaScript only. + * + * evalInSandbox evaluates the provided source string in the given sandbox. + * It returns the result of the evaluation to the caller. + * + * var s = new C.u.Sandbox("http://www.mozilla.org"); + * var res = C.u.evalInSandbox("var five = 5; 2 + five", s); + * var outerFive = s.five; + * s.seven = res; + * var thirtyFive = C.u.evalInSandbox("five * seven", s); + */ + [implicit_jscontext,optional_argc] + jsval evalInSandbox(in AString source, in jsval sandbox, + [optional] in jsval version, + [optional] in AUTF8String filename, + [optional] in long lineNo); + + /* + * getSandboxAddonId is designed to be called from JavaScript only. + * + * getSandboxAddonId retrieves the add-on ID associated with + * a sandbox object. It will return undefined if there + * is no add-on ID attached to the sandbox. + * + * var s = C.u.Sandbox(..., { addonId: "123" }); + * var id = C.u.getSandboxAddonId(s); + */ + [implicit_jscontext] + jsval getSandboxAddonId(in jsval sandbox); + + /* + * getSandboxMetadata is designed to be called from JavaScript only. + * + * getSandboxMetadata retrieves the metadata associated with + * a sandbox object. It will return undefined if there + * is no metadata attached to the sandbox. + * + * var s = C.u.Sandbox(..., { metadata: "metadata" }); + * var metadata = C.u.getSandboxMetadata(s); + */ + [implicit_jscontext] + jsval getSandboxMetadata(in jsval sandbox); + + /* + * setSandboxMetadata is designed to be called from JavaScript only. + * + * setSandboxMetadata sets the metadata associated with + * a sandbox object. + * + * Note that the metadata object will be copied before being used. + * The copy will be performed using the structured clone algorithm. + * Note that this algorithm does not support reflectors and + * it will throw if it encounters them. + */ + [implicit_jscontext] + void setSandboxMetadata(in jsval sandbox, in jsval metadata); + + /* + * import is designed to be called from JavaScript only. + * + * Synchronously loads and evaluates the js file located at + * 'registryLocation' with a new, fully privileged global object. + * + * If 'targetObj' is specified and equal to null, returns the + * module's global object. Otherwise (if 'targetObj' is not + * specified, or 'targetObj' is != null) looks for a property + * 'EXPORTED_SYMBOLS' on the new global object. 'EXPORTED_SYMBOLS' + * is expected to be an array of strings identifying properties on + * the global object. These properties will be installed as + * properties on 'targetObj', or, if 'targetObj' is not specified, + * on the caller's global object. If 'EXPORTED_SYMBOLS' is not + * found, an error is thrown. + * + * @param resourceURI A resource:// URI string to load the module from. + * @param targetObj the object to install the exported properties on. + * If this parameter is a primitive value, this method throws + * an exception. + * @returns the module code's global object. + * + * The implementation maintains a hash of registryLocation->global obj. + * Subsequent invocations of importModule with 'registryLocation' + * pointing to the same file will not cause the module to be re-evaluated, + * but the symbols in EXPORTED_SYMBOLS will be exported into the + * specified target object and the global object returned as above. + * + * (This comment is duplicated from xpcIJSModuleLoader.) + */ + [implicit_jscontext,optional_argc] + jsval import(in AUTF8String aResourceURI, [optional] in jsval targetObj); + + /** + * Returns true if the js file located at 'registryLocation' location has + * been loaded previously via the import method above. Returns false + * otherwise. + * + * @param resourceURI A resource:// URI string representing the location of + * the js file to be checked if it is already loaded or not. + * @returns boolean, true if the js file has been loaded via import. false + * otherwise + */ + boolean isModuleLoaded(in AUTF8String aResourceURI); + + /* + * Unloads the JS module at 'registryLocation'. Existing references to the + * module will continue to work but any subsequent import of the module will + * reload it and give new reference. If the JS module hasn't yet been + * imported then this method will do nothing. + * + * @param resourceURI A resource:// URI string to unload the module from. + */ + void unload(in AUTF8String registryLocation); + + /* + * Imports global properties (like DOM constructors) into the scope, defining + * them on the caller's global. aPropertyList should be an array of property + * names. + * + * See xpc::GlobalProperties::Parse for the current list of supported + * properties. + */ + [implicit_jscontext] + void importGlobalProperties(in jsval aPropertyList); + + /* + * To be called from JS only. + * + * Return a weak reference for the given JS object. + */ + [implicit_jscontext] + xpcIJSWeakReference getWeakReference(in jsval obj); + + /* + * To be called from JS only. + * + * Force an immediate garbage collection cycle. + */ + void forceGC(); + + /* + * To be called from JS only. + * + * Force an immediate cycle collection cycle. + */ + void forceCC([optional] in nsICycleCollectorListener aListener); + + /* + * To be called from JS only. + * + * If any incremental CC is in progress, finish it. For testing. + */ + void finishCC(); + + /* + * To be called from JS only. + * + * Do some cycle collector work, with the given work budget. + * The cost of calling Traverse() on a single object is set as 1. + * For testing. + */ + void ccSlice(in long long budget); + + /* + * To be called from JS only. + * + * Return the longest cycle collector slice time since the last + * time clearMaxCCTime() was called. + */ + long getMaxCCSliceTimeSinceClear(); + + /* + * To be called from JS only. + * + * Reset the internal max slice time value used for + * getMaxCCSliceTimeSinceClear(). + */ + void clearMaxCCTime(); + + /* + * To be called from JS only. + * + * Force an immediate shrinking garbage collection cycle. + */ + void forceShrinkingGC(); + + /* + * Schedule a garbage collection cycle for a point in the future when no JS + * is running. Call the provided function once this has occurred. + */ + void schedulePreciseGC(in ScheduledGCCallback callback); + + /* + * Schedule a shrinking garbage collection cycle for a point in the future + * when no JS is running. Call the provided function once this has occured. + */ + void schedulePreciseShrinkingGC(in ScheduledGCCallback callback); + + /* + * In a debug build, unlink any ghost windows. This is only for debugging + * leaks, and can cause bad things to happen if called. + */ + void unlinkGhostWindows(); + + [implicit_jscontext] + jsval getJSTestingFunctions(); + + /* + * To be called from JS only. + * + * Call 'function', using the provided stack as the async stack responsible + * for the call, and propagate its return value or the exception it throws. + * The function is called with no arguments, and 'this' is 'undefined'. + * + * The code in the function will see the given stack frame as the + * asyncCaller of its own stack frame, instead of the current caller. + */ + [implicit_jscontext] + jsval callFunctionWithAsyncStack(in jsval function, in nsIStackFrame stack, + in AString asyncCause); + + /* + * To be called from JS only. + * + * Returns the global object with which the given object is associated. + * + * @param obj The JavaScript object whose global is to be gotten. + * @return the corresponding global. + */ + [implicit_jscontext] + jsval getGlobalForObject(in jsval obj); + + /* + * To be called from JS only. + * + * Returns the true if the object is a (scripted) proxy. + * NOTE: Security wrappers are unwrapped first before the check. + */ + [implicit_jscontext] + boolean isProxy(in jsval vobject); + + /* + * To be called from JS only. + * + * Instead of simply wrapping a function into another compartment, + * this helper function creates a native function in the target + * compartment and forwards the call to the original function. + * That call will be different than a regular JS function call in + * that, the |this| is left unbound, and all the non-native JS + * object arguments will be cloned using the structured clone + * algorithm. + * The return value is the new forwarder function, wrapped into + * the caller's compartment. + * The 3rd argument is an optional options object: + * - defineAs: the name of the property that will + * be set on the target scope, with + * the forwarder function as the value. + */ + [implicit_jscontext] + jsval exportFunction(in jsval vfunction, in jsval vscope, [optional] in jsval voptions); + + /* + * To be called from JS only. + * + * Returns an object created in |vobj|'s compartment. + * If defineAs property on the options object is a non-null ID, + * the new object will be added to vobj as a property. Also, the + * returned new object is always automatically waived (see waiveXrays). + */ + [implicit_jscontext] + jsval createObjectIn(in jsval vobj, [optional] in jsval voptions); + + /* + * To be called from JS only. + * + * Ensures that all functions come from vobj's scope (and aren't cross + * compartment wrappers). + */ + [implicit_jscontext] + void makeObjectPropsNormal(in jsval vobj); + + /** + * Determines whether this object is backed by a DeadObjectProxy. + * + * Dead-wrapper objects hold no other objects alive (they have no outgoing + * reference edges) and will throw if you touch them (e.g. by + * reading/writing a property). + */ + bool isDeadWrapper(in jsval obj); + + /** + * Determines whether this object is a cross-process wrapper. + */ + 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); + + /** + * If CPOWs are disabled for browser code via the + * dom.ipc.cpows.forbid-unsafe-from-browser preferences, then only + * add-ons can use CPOWs. This function allows a non-addon scope + * to opt into CPOWs. It's necessary for the implementation of + * RemoteAddonsParent.jsm. + */ + void permitCPOWsInScope(in jsval obj); + + /* + * To be called from JS only. This is for Gecko internal use only, and may + * disappear at any moment. + * + * Forces a recomputation of all wrappers in and out of the compartment + * containing |vobj|. If |vobj| is not an object, all wrappers system-wide + * are recomputed. + */ + [implicit_jscontext] + void recomputeWrappers([optional] in jsval vobj); + + /* + * To be called from JS only. This is for Gecko internal use only, and may + * disappear at any moment. + * + * Enables Xray vision for same-compartment access for the compartment + * indicated by |vscope|. All outgoing wrappers are recomputed. + */ + [implicit_jscontext] + void setWantXrays(in jsval vscope); + + /* + * For gecko internal automation use only. Calling this in production code + * would result in security vulnerabilities, so it will crash if used outside + * of automation. + */ + [implicit_jscontext] + void forcePermissiveCOWs(); + + /* + * Forces the usage of a privileged |Components| object for a potentially- + * unprivileged scope. This will crash if used outside of automation. + */ + [implicit_jscontext] + void forcePrivilegedComponentsForScope(in jsval vscope); + + /* + * This seemingly-paradoxical API allows privileged code to explicitly give + * unprivileged code a reference to its own Components object (whereas it's + * normally hidden away on a scope chain visible only to XBL methods). See + * also SpecialPowers.getComponents. + */ + [implicit_jscontext] + jsval getComponentsForScope(in jsval vscope); + + /* + * Dispatches a runnable to the current/main thread. If |scope| is passed, + * the runnable will be dispatch in the compartment of |scope|, which + * affects which error reporter gets called. + */ + [implicit_jscontext] + void dispatch(in jsval runnable, [optional] in jsval scope); + + /* + * To be called from JS only. + * + * These are the set of JSContext options that privileged script + * is allowed to control for the purposes of testing. These + * options should be kept in sync with what's controllable in the + * jsshell and by setting prefs in nsJSEnvironment. + * + * NB: Assume that getting any of these attributes is relatively + * cheap, but setting any of them is relatively expensive. + */ + [implicit_jscontext] + attribute boolean strict; + + [implicit_jscontext] + attribute boolean werror; + + [implicit_jscontext] + attribute boolean strict_mode; + + [implicit_jscontext] + attribute boolean ion; + + [implicit_jscontext] + void setGCZeal(in long zeal); + + [implicit_jscontext] + void nukeSandbox(in jsval obj); + + /* + * API to dynamically block script for a given global. This takes effect + * immediately, unlike other APIs that only affect newly-created globals. + * + * The machinery here maintains a counter, and allows script only if each + * call to blockScriptForGlobal() has been matched with a call to + * unblockScriptForGlobal(). The caller _must_ make sure never to call + * unblock() more times than it calls block(), since that could potentially + * interfere with another consumer's script blocking. + */ + + [implicit_jscontext] + void blockScriptForGlobal(in jsval global); + + [implicit_jscontext] + void unblockScriptForGlobal(in jsval global); + + /** + * Check whether the given object is an XrayWrapper. + */ + bool isXrayWrapper(in jsval obj); + + /** + * Waive Xray on a given value. Identity op for primitives. + */ + [implicit_jscontext] + jsval waiveXrays(in jsval aVal); + + /** + * Strip off Xray waivers on a given value. Identity op for primitives. + */ + [implicit_jscontext] + jsval unwaiveXrays(in jsval aVal); + + /** + * Gets the name of the JSClass of the object. + * + * if |aUnwrap| is true, all wrappers are unwrapped first. Unless you're + * specifically trying to detect whether the object is a proxy, this is + * probably what you want. + */ + [implicit_jscontext] + string getClassName(in jsval aObj, in bool aUnwrap); + + /** + * Get a DOM classinfo for the given classname. Only some class + * names are supported. + */ + nsIClassInfo getDOMClassInfo(in AString aClassName); + + /** + * Gets the incument global for the execution of this function. For internal + * and testing use only. + * + * If |callback| is passed, it is invoked with the incumbent global as its + * sole argument. This allows the incumbent global to be measured in callback + * environments with no scripted frames on the stack. + */ + [implicit_jscontext] + jsval getIncumbentGlobal([optional] in jsval callback); + + /** + * Forces the generation of an XPCWrappedJS for a given object. For internal + * and testing use only. This is only useful to set up wrapper map conditions + * for a testcase. The return value is not an XPCWrappedJS itself, but an + * opaque nsISupports holder that keeps the underlying XPCWrappedJS alive. + * + * if |scope| is passed, the XPCWrappedJS is generated in the scope of that object. + */ + [implicit_jscontext] + nsISupports generateXPCWrappedJS(in jsval obj, [optional] in jsval scope); + + /** + * Retrieve the last time, in microseconds since epoch, that a given + * watchdog-related event occured. + * + * Valid categories: + * "ContextStateChange" - Context switching between active and inactive states + * "WatchdogWakeup" - Watchdog waking up from sleeping + * "WatchdogHibernateStart" - Watchdog begins hibernating + * "WatchdogHibernateStop" - Watchdog stops hibernating + */ + PRTime getWatchdogTimestamp(in AString aCategory); + + [implicit_jscontext] + jsval getJSEngineTelemetryValue(); + + /* + * Clone an object into a scope. + * The 3rd argument is an optional options object: + * - cloneFunctions: boolean. If true, functions in the value are + * wrapped in a function forwarder that appears to be a native function in + * the content scope. Defaults to false. + * - wrapReflectors: boolean. If true, DOM objects are passed through the + * clone directly with cross-compartment wrappers. Otherwise, the clone + * fails when such an object is encountered. Defaults to false. + */ + [implicit_jscontext] + jsval cloneInto(in jsval value, in jsval scope, [optional] in jsval options); + + /* + * When C++-Implemented code does security checks, it can generally query + * the subject principal (i.e. the principal of the most-recently-executed + * script) in order to determine the responsible party. However, when an API + * is implemented in JS, this doesn't work - the most-recently-executed + * script is always the System-Principaled API implementation. So we need + * another mechanism. + * + * Hence the notion of the "WebIDL Caller". If the current Entry Script on + * the Script Settings Stack represents the invocation of JS-implemented + * WebIDL, this API returns the principal of the caller at the time + * of invocation. Otherwise (i.e. outside of JS-implemented WebIDL), this + * function throws. If it throws, you probably shouldn't be using it. + */ + nsIPrincipal getWebIDLCallerPrincipal(); + + /* + * Gets the principal of a script object, after unwrapping any cross- + * compartment wrappers. + */ + [implicit_jscontext] + nsIPrincipal getObjectPrincipal(in jsval obj); + + /* + * Gets the URI or identifier string associated with an object's + * compartment (the same one used by the memory reporter machinery). + * + * Unwraps cross-compartment wrappers first. + * + * The string formats and values may change at any time. Do not depend on + * this from addon code. + */ + [implicit_jscontext] + ACString getCompartmentLocation(in jsval obj); + + [implicit_jscontext] + void setAddonInterposition(in ACString addonId, in nsIAddonInterposition interposition); + + /* + * Enables call interpositions from addon scopes to any functions in the scope of |target|. + */ + [implicit_jscontext] + void setAddonCallInterposition(in jsval target); + + [implicit_jscontext] + void allowCPOWsInAddon(in ACString addonId, in bool allow); + + /* + * Return a fractional number of milliseconds from process + * startup, measured with a monotonic clock. + */ + double now(); +}; + +/** +* Interface for the 'Components' object. +* +* The first interface contains things that are available to non-chrome XBL code +* that runs in a scope with an nsExpandedPrincipal. The second interface +* includes members that are only exposed to chrome. +*/ + +[scriptable, uuid(eeeada2f-86c0-4609-b2bf-4bf2351b1ce6)] +interface nsIXPCComponentsBase : nsISupports +{ + readonly attribute nsIXPCComponents_Interfaces interfaces; + readonly attribute nsIXPCComponents_InterfacesByID interfacesByID; + readonly attribute nsIXPCComponents_Results results; + + boolean isSuccessCode(in nsresult result); + +}; + +[scriptable, uuid(aa28aaf6-70ce-4b03-9514-afe43c7dfda8)] +interface nsIXPCComponents : nsIXPCComponentsBase +{ + readonly attribute nsIXPCComponents_Classes classes; + readonly attribute nsIXPCComponents_ClassesByID classesByID; + // Will return null if there is no JS stack right now. + readonly attribute nsIStackFrame stack; + readonly attribute nsIComponentManager manager; + readonly attribute nsIXPCComponents_Utils utils; + + readonly attribute nsIXPCComponents_ID ID; + readonly attribute nsIXPCComponents_Exception Exception; + readonly attribute nsIXPCComponents_Constructor Constructor; + + [implicit_jscontext] + // A javascript component can set |returnCode| to specify an nsresult to + // be returned without throwing an exception. + attribute jsval returnCode; + + /* @deprecated Use Components.utils.reportError instead. */ + [deprecated, implicit_jscontext] void reportError(in jsval error); +}; diff --git a/js/xpconnect/idl/xpcexception.idl b/js/xpconnect/idl/xpcexception.idl new file mode 100644 index 000000000..d9f4c92fd --- /dev/null +++ b/js/xpconnect/idl/xpcexception.idl @@ -0,0 +1,30 @@ + /* -*- 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" +#include "nsIException.idl" + +[scriptable, builtinclass, uuid(875e6645-e762-4da6-9ec8-bf19ab0050df)] +interface nsIXPCException : nsIException +{ + // inherits methods from nsIException + + void initialize(in AUTF8String aMessage, + in nsresult aResult, + in AUTF8String aName, + in nsIStackFrame aLocation, + in nsISupports aData); +}; + +/* this goes into the C++ header verbatim. */ +%{ C++ +/********************************************************/ +// {5632BF70-51EC-11d3-9896-006008962422} +#define NS_XPCEXCEPTION_CID \ +{ 0x5632bf70, 0x51ec, 0x11d3, \ + { 0x98, 0x96, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 } } +%} + diff --git a/js/xpconnect/idl/xpcjsid.idl b/js/xpconnect/idl/xpcjsid.idl new file mode 100644 index 000000000..602d9b8e1 --- /dev/null +++ b/js/xpconnect/idl/xpcjsid.idl @@ -0,0 +1,47 @@ +/* -*- Mode: IDL; 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" + +[ptr] native const_nsID_ptr(const nsID); + +[builtinclass, scriptable, uuid(62883d14-4146-4039-94f5-a5e1e1a51a15)] +interface nsIJSID : nsISupports +{ + readonly attribute string name; + readonly attribute string number; + readonly attribute boolean valid; + + boolean equals(in nsIJSID other); + string toString(); + + [noscript] void initialize(in string idString); + + // returns a pointer to the internal nsID. this pointer is only valid + // while the nsIJSID object remains alive! + [notxpcom] const_nsID_ptr getID(); +}; + +[builtinclass, scriptable, uuid(e76ec564-a080-4705-8609-384c755ec91e)] +interface nsIJSIID : nsIJSID +{ +}; + +[builtinclass, scriptable, uuid(bf5eb086-9eaa-4694-aec3-fe4aac6119bd)] +interface nsIJSCID : nsIJSID +{ + [implicit_jscontext,optional_argc] jsval createInstance([optional] in jsval iid); + [implicit_jscontext,optional_argc] jsval getService([optional] in jsval iid); +}; + +/* this goes into the C++ header verbatim. */ +%{ C++ +/********************************************************/ +// {F24A14F0-4FA1-11d3-9894-006008962422} +#define NS_JS_ID_CID \ +{ 0xf24a14f0, 0x4fa1, 0x11d3, \ + { 0x98, 0x94, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 } } +%} + diff --git a/js/xpconnect/loader/ISO8601DateUtils.jsm b/js/xpconnect/loader/ISO8601DateUtils.jsm new file mode 100644 index 000000000..3eff78501 --- /dev/null +++ b/js/xpconnect/loader/ISO8601DateUtils.jsm @@ -0,0 +1,144 @@ +/* -*- 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 HOURS_TO_MINUTES = 60; +const MINUTES_TO_SECONDS = 60; +const SECONDS_TO_MILLISECONDS = 1000; +const MINUTES_TO_MILLISECONDS = MINUTES_TO_SECONDS * SECONDS_TO_MILLISECONDS; +const HOURS_TO_MILLISECONDS = HOURS_TO_MINUTES * MINUTES_TO_MILLISECONDS; + +this.EXPORTED_SYMBOLS = ["ISO8601DateUtils"]; + +debug("*** loading ISO8601DateUtils\n"); + +this.ISO8601DateUtils = { + + /** + * XXX Thunderbird's W3C-DTF function + * + * Converts a W3C-DTF (subset of ISO 8601) date string to a Javascript + * date object. W3C-DTF is described in this note: + * http://www.w3.org/TR/NOTE-datetime IETF is obtained via the Date + * object's toUTCString() method. The object's toString() method is + * insufficient because it spells out timezones on Win32 + * (f.e. "Pacific Standard Time" instead of "PST"), which Mail doesn't + * grok. For info, see + * http://lxr.mozilla.org/mozilla/source/js/src/jsdate.c#1526. + */ + parse: function ISO8601_parse(aDateString) { + var dateString = aDateString; + if (!dateString.match('-')) { + // Workaround for server sending + // dates such as: 20030530T11:18:50-08:00 + // instead of: 2003-05-30T11:18:50-08:00 + var year = dateString.slice(0, 4); + var month = dateString.slice(4, 6); + var rest = dateString.slice(6, dateString.length); + dateString = year + "-" + month + "-" + rest; + } + + var parts = dateString.match(/(\d{4})(-(\d{2,3}))?(-(\d{2}))?(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|([+-])(\d{2}):(\d{2}))?)?/); + + // Here's an example of a W3C-DTF date string and what .match returns for it. + // + // date: 2003-05-30T11:18:50.345-08:00 + // date.match returns array values: + // + // 0: 2003-05-30T11:18:50-08:00, + // 1: 2003, + // 2: -05, + // 3: 05, + // 4: -30, + // 5: 30, + // 6: T11:18:50-08:00, + // 7: 11, + // 8: 18, + // 9: :50, + // 10: 50, + // 11: .345, + // 12: 345, + // 13: -08:00, + // 14: -, + // 15: 08, + // 16: 00 + + // Create a Date object from the date parts. Note that the Date + // object apparently can't deal with empty string parameters in lieu + // of numbers, so optional values (like hours, minutes, seconds, and + // milliseconds) must be forced to be numbers. + var date = new Date(parts[1], parts[3] - 1, parts[5], parts[7] || 0, + parts[8] || 0, parts[10] || 0, parts[12] || 0); + + // We now have a value that the Date object thinks is in the local + // timezone but which actually represents the date/time in the + // remote timezone (f.e. the value was "10:00 EST", and we have + // converted it to "10:00 PST" instead of "07:00 PST"). We need to + // correct that. To do so, we're going to add the offset between + // the remote timezone and UTC (to convert the value to UTC), then + // add the offset between UTC and the local timezone //(to convert + // the value to the local timezone). + + // Ironically, W3C-DTF gives us the offset between UTC and the + // remote timezone rather than the other way around, while the + // getTimezoneOffset() method of a Date object gives us the offset + // between the local timezone and UTC rather than the other way + // around. Both of these are the additive inverse (i.e. -x for x) + // of what we want, so we have to invert them to use them by + // multipying by -1 (f.e. if "the offset between UTC and the remote + // timezone" is -5 hours, then "the offset between the remote + // timezone and UTC" is -5*-1 = 5 hours). + + // Note that if the timezone portion of the date/time string is + // absent (which violates W3C-DTF, although ISO 8601 allows it), we + // assume the value to be in UTC. + + // The offset between the remote timezone and UTC in milliseconds. + var remoteToUTCOffset = 0; + if (parts[13] && parts[13] != "Z") { + var direction = (parts[14] == "+" ? 1 : -1); + if (parts[15]) + remoteToUTCOffset += direction * parts[15] * HOURS_TO_MILLISECONDS; + if (parts[16]) + remoteToUTCOffset += direction * parts[16] * MINUTES_TO_MILLISECONDS; + } + remoteToUTCOffset = remoteToUTCOffset * -1; // invert it + + // The offset between UTC and the local timezone in milliseconds. + var UTCToLocalOffset = date.getTimezoneOffset() * MINUTES_TO_MILLISECONDS; + UTCToLocalOffset = UTCToLocalOffset * -1; // invert it + date.setTime(date.getTime() + remoteToUTCOffset + UTCToLocalOffset); + + return date; + }, + + create: function ISO8601_create(aDate) { + function zeropad (s, l) { + s = s.toString(); // force it to a string + while (s.length < l) { + s = '0' + s; + } + return s; + } + + var myDate; + // if d is a number, turn it into a date + if (typeof aDate == 'number') { + myDate = new Date() + myDate.setTime(aDate); + } else { + myDate = aDate; + } + + // YYYY-MM-DDThh:mm:ssZ + var result = zeropad(myDate.getUTCFullYear (), 4) + + zeropad(myDate.getUTCMonth () + 1, 2) + + zeropad(myDate.getUTCDate (), 2) + 'T' + + zeropad(myDate.getUTCHours (), 2) + ':' + + zeropad(myDate.getUTCMinutes (), 2) + ':' + + zeropad(myDate.getUTCSeconds (), 2) + 'Z'; + + return result; + } +} diff --git a/js/xpconnect/loader/XPCOMUtils.jsm b/js/xpconnect/loader/XPCOMUtils.jsm new file mode 100644 index 000000000..eb35258de --- /dev/null +++ b/js/xpconnect/loader/XPCOMUtils.jsm @@ -0,0 +1,471 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * vim: sw=2 ts=2 sts=2 et filetype=javascript + * 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/. */ + +/** + * Utilities for JavaScript components loaded by the JS component + * loader. + * + * Import into a JS component using + * 'Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");' + * + * Exposing a JS 'class' as a component using these utility methods consists + * of several steps: + * 0. Import XPCOMUtils, as described above. + * 1. Declare the 'class' (or multiple classes) implementing the component(s): + * function MyComponent() { + * // constructor + * } + * MyComponent.prototype = { + * // properties required for XPCOM registration: + * classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), + * + * // [optional] custom factory (an object implementing nsIFactory). If not + * // provided, the default factory is used, which returns + * // |(new MyComponent()).QueryInterface(iid)| in its createInstance(). + * _xpcom_factory: { ... }, + * + * // QueryInterface implementation, e.g. using the generateQI helper + * QueryInterface: XPCOMUtils.generateQI( + * [Components.interfaces.nsIObserver, + * Components.interfaces.nsIMyInterface, + * "nsIFoo", + * "nsIBar" ]), + * + * // [optional] classInfo implementation, e.g. using the generateCI helper. + * // Will be automatically returned from QueryInterface if that was + * // generated with the generateQI helper. + * classInfo: XPCOMUtils.generateCI( + * {classID: Components.ID("{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"), + * contractID: "@example.com/xxx;1", + * classDescription: "unique text description", + * interfaces: [Components.interfaces.nsIObserver, + * Components.interfaces.nsIMyInterface, + * "nsIFoo", + * "nsIBar"], + * flags: Ci.nsIClassInfo.SINGLETON}), + * + * // The following properties were used prior to Mozilla 2, but are no + * // longer supported. They may still be included for compatibility with + * // prior versions of XPCOMUtils. In Mozilla 2, this information is + * // included in the .manifest file which registers this JS component. + * classDescription: "unique text description", + * contractID: "@example.com/xxx;1", + * + * // [optional] an array of categories to register this component in. + * _xpcom_categories: [{ + * // Each object in the array specifies the parameters to pass to + * // nsICategoryManager.addCategoryEntry(). 'true' is passed for + * // both aPersist and aReplace params. + * category: "some-category", + * // optional, defaults to the object's classDescription + * entry: "entry name", + * // optional, defaults to the object's contractID (unless + * // 'service' is specified) + * value: "...", + * // optional, defaults to false. When set to true, and only if 'value' + * // is not specified, the concatenation of the string "service," and the + * // object's contractID is passed as aValue parameter of addCategoryEntry. + * service: true, + * // optional, it can be an array of applications' IDs in the form: + * // [ "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}", ... ] + * // If defined the component will be registered in this category only for + * // the provided applications. + * apps: [...] + * }], + * + * // ...component implementation... + * }; + * + * 2. Create an array of component constructors (like the one + * created in step 1): + * var components = [MyComponent]; + * + * 3. Define the NSGetFactory entry point: + * this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components); + */ + + +this.EXPORTED_SYMBOLS = [ "XPCOMUtils" ]; + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cr = Components.results; +const Cu = Components.utils; + +this.XPCOMUtils = { + /** + * Generate a QueryInterface implementation. The returned function must be + * assigned to the 'QueryInterface' property of a JS object. When invoked on + * that object, it checks if the given iid is listed in the |interfaces| + * param, and if it is, returns |this| (the object it was called on). + * If the JS object has a classInfo property it'll be returned for the + * nsIClassInfo IID, generateCI can be used to generate the classInfo + * property. + */ + generateQI: function XPCU_generateQI(interfaces) { + /* Note that Ci[Ci.x] == Ci.x for all x */ + let a = []; + if (interfaces) { + for (let i = 0; i < interfaces.length; i++) { + let iface = interfaces[i]; + if (Ci[iface]) { + a.push(Ci[iface].name); + } + } + } + return makeQI(a); + }, + + /** + * Generate a ClassInfo implementation for a component. The returned object + * must be assigned to the 'classInfo' property of a JS object. The first and + * only argument should be an object that contains a number of optional + * properties: "interfaces", "contractID", "classDescription", "classID" and + * "flags". The values of the properties will be returned as the values of the + * various properties of the nsIClassInfo implementation. + */ + generateCI: function XPCU_generateCI(classInfo) + { + if (QueryInterface in classInfo) + throw Error("In generateCI, don't use a component for generating classInfo"); + /* Note that Ci[Ci.x] == Ci.x for all x */ + let _interfaces = []; + for (let i = 0; i < classInfo.interfaces.length; i++) { + let iface = classInfo.interfaces[i]; + if (Ci[iface]) { + _interfaces.push(Ci[iface]); + } + } + return { + getInterfaces: function XPCU_getInterfaces(countRef) { + countRef.value = _interfaces.length; + return _interfaces; + }, + getScriptableHelper: function XPCU_getScriptableHelper() { + return null; + }, + contractID: classInfo.contractID, + classDescription: classInfo.classDescription, + classID: classInfo.classID, + flags: classInfo.flags, + QueryInterface: this.generateQI([Ci.nsIClassInfo]) + }; + }, + + /** + * Generate a NSGetFactory function given an array of components. + */ + generateNSGetFactory: function XPCU_generateNSGetFactory(componentsArray) { + let classes = {}; + for (let i = 0; i < componentsArray.length; i++) { + let component = componentsArray[i]; + if (!(component.prototype.classID instanceof Components.ID)) + throw Error("In generateNSGetFactory, classID missing or incorrect for component " + component); + + classes[component.prototype.classID] = this._getFactory(component); + } + return function NSGetFactory(cid) { + let cidstring = cid.toString(); + if (cidstring in classes) + return classes[cidstring]; + throw Cr.NS_ERROR_FACTORY_NOT_REGISTERED; + } + }, + + /** + * Defines a getter on a specified object that will be created upon first use. + * + * @param aObject + * The object to define the lazy getter on. + * @param aName + * The name of the getter to define on aObject. + * @param aLambda + * A function that returns what the getter should return. This will + * only ever be called once. + */ + defineLazyGetter: function XPCU_defineLazyGetter(aObject, aName, aLambda) + { + Object.defineProperty(aObject, aName, { + get: function () { + // Redefine this accessor property as a data property. + // Delete it first, to rule out "too much recursion" in case aObject is + // a proxy whose defineProperty handler might unwittingly trigger this + // getter again. + delete aObject[aName]; + let value = aLambda.apply(aObject); + Object.defineProperty(aObject, aName, { + value, + writable: true, + configurable: true, + enumerable: true + }); + return value; + }, + configurable: true, + enumerable: true + }); + }, + + /** + * Defines a getter on a specified object for a service. The service will not + * be obtained until first use. + * + * @param aObject + * The object to define the lazy getter on. + * @param aName + * The name of the getter to define on aObject for the service. + * @param aContract + * The contract used to obtain the service. + * @param aInterfaceName + * The name of the interface to query the service to. + */ + defineLazyServiceGetter: function XPCU_defineLazyServiceGetter(aObject, aName, + aContract, + aInterfaceName) + { + this.defineLazyGetter(aObject, aName, function XPCU_serviceLambda() { + return Cc[aContract].getService(Ci[aInterfaceName]); + }); + }, + + /** + * Defines a getter on a specified object for a module. The module will not + * be imported until first use. The getter allows to execute setup and + * teardown code (e.g. to register/unregister to services) and accepts + * a proxy object which acts on behalf of the module until it is imported. + * + * @param aObject + * The object to define the lazy getter on. + * @param aName + * The name of the getter to define on aObject for the module. + * @param aResource + * The URL used to obtain the module. + * @param aSymbol + * The name of the symbol exported by the module. + * This parameter is optional and defaults to aName. + * @param aPreLambda + * A function that is executed when the proxy is set up. + * This will only ever be called once. + * @param aPostLambda + * A function that is executed when the module has been imported to + * run optional teardown procedures on the proxy object. + * This will only ever be called once. + * @param aProxy + * An object which acts on behalf of the module to be imported until + * the module has been imported. + */ + defineLazyModuleGetter: function XPCU_defineLazyModuleGetter( + aObject, aName, aResource, aSymbol, + aPreLambda, aPostLambda, aProxy) + { + let proxy = aProxy || {}; + + if (typeof(aPreLambda) === "function") { + aPreLambda.apply(proxy); + } + + this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() { + var temp = {}; + try { + Cu.import(aResource, temp); + + if (typeof(aPostLambda) === "function") { + aPostLambda.apply(proxy); + } + } catch (ex) { + Cu.reportError("Failed to load module " + aResource + "."); + throw ex; + } + return temp[aSymbol || aName]; + }); + }, + + /** + * Defines a getter on a specified object for preference value. The + * preference is read the first time that the property is accessed, + * and is thereafter kept up-to-date using a preference observer. + * + * @param aObject + * The object to define the lazy getter on. + * @param aName + * The name of the getter property to define on aObject. + * @param aPreference + * The name of the preference to read. + * @param aDefaultValue + * The default value to use, if the preference is not defined. + */ + defineLazyPreferenceGetter: function XPCU_defineLazyPreferenceGetter( + aObject, aName, aPreference, aDefaultValue = null) + { + // Note: We need to keep a reference to this observer alive as long + // as aObject is alive. This means that all of our getters need to + // explicitly close over the variable that holds the object, and we + // cannot define a value in place of a getter after we read the + // preference. + let observer = { + QueryInterface: this.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]), + + value: undefined, + + observe(subject, topic, data) { + if (data == aPreference) { + this.value = undefined; + } + }, + } + + let defineGetter = get => { + Object.defineProperty(aObject, aName, { + configurable: true, + enumerable: true, + get, + }); + }; + + function lazyGetter() { + if (observer.value === undefined) { + observer.value = Preferences.get(aPreference, aDefaultValue); + } + return observer.value; + } + + defineGetter(() => { + Services.prefs.addObserver(aPreference, observer, true); + + defineGetter(lazyGetter); + return lazyGetter(); + }); + }, + + /** + * Helper which iterates over a nsISimpleEnumerator. + * @param e The nsISimpleEnumerator to iterate over. + * @param i The expected interface for each element. + */ + IterSimpleEnumerator: function* XPCU_IterSimpleEnumerator(e, i) + { + while (e.hasMoreElements()) + yield e.getNext().QueryInterface(i); + }, + + /** + * Helper which iterates over a string enumerator. + * @param e The string enumerator (nsIUTF8StringEnumerator or + * nsIStringEnumerator) over which to iterate. + */ + IterStringEnumerator: function* XPCU_IterStringEnumerator(e) + { + while (e.hasMore()) + yield e.getNext(); + }, + + /** + * Helper which iterates over the entries in a category. + * @param aCategory The name of the category over which to iterate. + */ + enumerateCategoryEntries: function* XPCOMUtils_enumerateCategoryEntries(aCategory) + { + let category = this.categoryManager.enumerateCategory(aCategory); + for (let entry of this.IterSimpleEnumerator(category, Ci.nsISupportsCString)) { + yield [entry.data, this.categoryManager.getCategoryEntry(aCategory, entry.data)]; + } + }, + + /** + * Returns an nsIFactory for |component|. + */ + _getFactory: function XPCOMUtils__getFactory(component) { + var factory = component.prototype._xpcom_factory; + if (!factory) { + factory = { + createInstance: function(outer, iid) { + if (outer) + throw Cr.NS_ERROR_NO_AGGREGATION; + return (new component()).QueryInterface(iid); + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) + } + } + return factory; + }, + + /** + * Allows you to fake a relative import. Expects the global object from the + * module that's calling us, and the relative filename that we wish to import. + */ + importRelative: function XPCOMUtils__importRelative(that, path, scope) { + if (!("__URI__" in that)) + throw Error("importRelative may only be used from a JSM, and its first argument "+ + "must be that JSM's global object (hint: use this)"); + let uri = that.__URI__; + let i = uri.lastIndexOf("/"); + Components.utils.import(uri.substring(0, i+1) + path, scope || that); + }, + + /** + * generates a singleton nsIFactory implementation that can be used as + * the _xpcom_factory of the component. + * @param aServiceConstructor + * Constructor function of the component. + */ + generateSingletonFactory: + function XPCOMUtils_generateSingletonFactory(aServiceConstructor) { + return { + _instance: null, + createInstance: function XPCU_SF_createInstance(aOuter, aIID) { + if (aOuter !== null) { + throw Cr.NS_ERROR_NO_AGGREGATION; + } + if (this._instance === null) { + this._instance = new aServiceConstructor(); + } + return this._instance.QueryInterface(aIID); + }, + lockFactory: function XPCU_SF_lockFactory(aDoLock) { + throw Cr.NS_ERROR_NOT_IMPLEMENTED; + }, + QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]) + }; + }, + + /** + * Defines a non-writable property on an object. + */ + defineConstant: function XPCOMUtils__defineConstant(aObj, aName, aValue) { + Object.defineProperty(aObj, aName, { + value: aValue, + enumerable: true, + writable: false + }); + }, +}; + +XPCOMUtils.defineLazyModuleGetter(this, "Preferences", + "resource://gre/modules/Preferences.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyServiceGetter(XPCOMUtils, "categoryManager", + "@mozilla.org/categorymanager;1", + "nsICategoryManager"); + +/** + * Helper for XPCOMUtils.generateQI to avoid leaks - see bug 381651#c1 + */ +function makeQI(interfaceNames) { + return function XPCOMUtils_QueryInterface(iid) { + if (iid.equals(Ci.nsISupports)) + return this; + if (iid.equals(Ci.nsIClassInfo) && "classInfo" in this) + return this.classInfo; + for (let i = 0; i < interfaceNames.length; i++) { + if (Ci[interfaceNames[i]].equals(iid)) + return this; + } + + throw Cr.NS_ERROR_NO_INTERFACE; + }; +} diff --git a/js/xpconnect/loader/moz.build b/js/xpconnect/loader/moz.build new file mode 100644 index 000000000..3dc03d6db --- /dev/null +++ b/js/xpconnect/loader/moz.build @@ -0,0 +1,28 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# These files cannot be built in unified mode because they rely on plarena.h +SOURCES += [ + 'mozJSComponentLoader.cpp', + 'mozJSLoaderUtils.cpp', + 'mozJSSubScriptLoader.cpp', +] + +EXTRA_JS_MODULES += [ + 'ISO8601DateUtils.jsm', + 'XPCOMUtils.jsm', +] + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../src', + '../wrappers', + '/dom/base', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-shadow'] diff --git a/js/xpconnect/loader/mozJSComponentLoader.cpp b/js/xpconnect/loader/mozJSComponentLoader.cpp new file mode 100644 index 000000000..95c214867 --- /dev/null +++ b/js/xpconnect/loader/mozJSComponentLoader.cpp @@ -0,0 +1,1437 @@ + +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Attributes.h" + +#include + +#include "mozilla/Logging.h" +#ifdef ANDROID +#include +#endif +#ifdef XP_WIN +#include +#endif + +#include "jsapi.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsIComponentManager.h" +#include "mozilla/Module.h" +#include "nsIFile.h" +#include "mozJSComponentLoader.h" +#include "mozJSLoaderUtils.h" +#include "nsIXPConnect.h" +#include "nsIObserverService.h" +#include "nsIScriptSecurityManager.h" +#include "nsIFileURL.h" +#include "nsIJARURI.h" +#include "nsNetUtil.h" +#include "jsprf.h" +#include "nsJSPrincipals.h" +#include "nsJSUtils.h" +#include "xpcprivate.h" +#include "xpcpublic.h" +#include "nsContentUtils.h" +#include "nsXULAppAPI.h" +#include "WrapperFactory.h" + +#include "mozilla/AddonPathService.h" +#include "mozilla/scache/StartupCache.h" +#include "mozilla/scache/StartupCacheUtils.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" + +using namespace mozilla; +using namespace mozilla::scache; +using namespace xpc; +using namespace JS; + +// This JSClass exists to trick silly code that expects toString()ing the +// global in a component scope to return something with "BackstagePass" in it +// to continue working. +static const JSClass kFakeBackstagePassJSClass = { "FakeBackstagePass" }; + +static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1"; +static const char kObserverServiceContractID[] = "@mozilla.org/observer-service;1"; +static const char kJSCachePrefix[] = "jsloader"; + +#define HAVE_PR_MEMMAP + +/** + * Buffer sizes for serialization and deserialization of scripts. + * FIXME: bug #411579 (tune this macro!) Last updated: Jan 2008 + */ +#define XPC_SERIALIZATION_BUFFER_SIZE (64 * 1024) +#define XPC_DESERIALIZATION_BUFFER_SIZE (12 * 8192) + +// MOZ_LOG=JSComponentLoader:5 +static LazyLogModule gJSCLLog("JSComponentLoader"); + +#define LOG(args) MOZ_LOG(gJSCLLog, mozilla::LogLevel::Debug, args) + +// Components.utils.import error messages +#define ERROR_SCOPE_OBJ "%s - Second argument must be an object." +#define ERROR_NOT_PRESENT "%s - EXPORTED_SYMBOLS is not present." +#define ERROR_NOT_AN_ARRAY "%s - EXPORTED_SYMBOLS is not an array." +#define ERROR_GETTING_ARRAY_LENGTH "%s - Error getting array length of EXPORTED_SYMBOLS." +#define ERROR_ARRAY_ELEMENT "%s - EXPORTED_SYMBOLS[%d] is not a string." +#define ERROR_GETTING_SYMBOL "%s - Could not get symbol '%s'." +#define ERROR_SETTING_SYMBOL "%s - Could not set symbol '%s' on target object." + +static bool +Dump(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) + return true; + + RootedString str(cx, JS::ToString(cx, args[0])); + if (!str) + return false; + + JSAutoByteString utf8str; + if (!utf8str.encodeUtf8(cx, str)) + return false; + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.ptr()); +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + nsAutoJSString wstr; + if (!wstr.init(cx, str)) + return false; + OutputDebugStringW(wstr.get()); + } +#endif + fputs(utf8str.ptr(), stdout); + fflush(stdout); + return true; +} + +static bool +Debug(JSContext* cx, unsigned argc, Value* vp) +{ +#ifdef DEBUG + return Dump(cx, argc, vp); +#else + return true; +#endif +} + +static const JSFunctionSpec gGlobalFun[] = { + JS_FS("dump", Dump, 1,0), + JS_FS("debug", Debug, 1,0), + JS_FS("atob", Atob, 1,0), + JS_FS("btoa", Btoa, 1,0), + JS_FS_END +}; + +class MOZ_STACK_CLASS JSCLContextHelper +{ +public: + explicit JSCLContextHelper(JSContext* aCx); + ~JSCLContextHelper(); + + void reportErrorAfterPop(char* buf); + +private: + JSContext* mContext; + char* mBuf; + + // prevent copying and assignment + JSCLContextHelper(const JSCLContextHelper&) = delete; + const JSCLContextHelper& operator=(const JSCLContextHelper&) = delete; +}; + +static nsresult +MOZ_FORMAT_PRINTF(2, 3) +ReportOnCallerUTF8(JSContext* callerContext, + const char* format, ...) { + if (!callerContext) { + return NS_ERROR_FAILURE; + } + + va_list ap; + va_start(ap, format); + + char* buf = JS_vsmprintf(format, ap); + if (!buf) { + va_end(ap); + return NS_ERROR_OUT_OF_MEMORY; + } + + JS_ReportErrorUTF8(callerContext, "%s", buf); + JS_smprintf_free(buf); + + va_end(ap); + return NS_OK; +} + +static nsresult +MOZ_FORMAT_PRINTF(2, 3) +ReportOnCallerUTF8(JSCLContextHelper& helper, + const char* format, ...) +{ + va_list ap; + va_start(ap, format); + + char* buf = JS_vsmprintf(format, ap); + if (!buf) { + va_end(ap); + return NS_ERROR_OUT_OF_MEMORY; + } + + helper.reportErrorAfterPop(buf); + va_end(ap); + return NS_OK; +} + +mozJSComponentLoader::mozJSComponentLoader() + : mModules(16), + mImports(16), + mInProgressImports(16), + mInitialized(false), + mReuseLoaderGlobal(false) +{ + MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton"); + + sSelf = this; +} + +#define ENSURE_DEP(name) { nsresult rv = Ensure##name(); NS_ENSURE_SUCCESS(rv, rv); } +#define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__)); +#define BEGIN_ENSURE(self, ...) { \ + if (m##self) \ + return NS_OK; \ + ENSURE_DEPS(__VA_ARGS__); \ +} + +class MOZ_STACK_CLASS ComponentLoaderInfo { + public: + explicit ComponentLoaderInfo(const nsACString& aLocation) : mLocation(aLocation) {} + + nsIIOService* IOService() { MOZ_ASSERT(mIOService); return mIOService; } + nsresult EnsureIOService() { + if (mIOService) + return NS_OK; + nsresult rv; + mIOService = do_GetIOService(&rv); + return rv; + } + + nsIURI* URI() { MOZ_ASSERT(mURI); return mURI; } + nsresult EnsureURI() { + BEGIN_ENSURE(URI, IOService); + return mIOService->NewURI(mLocation, nullptr, nullptr, getter_AddRefs(mURI)); + } + + nsIChannel* ScriptChannel() { MOZ_ASSERT(mScriptChannel); return mScriptChannel; } + nsresult EnsureScriptChannel() { + BEGIN_ENSURE(ScriptChannel, IOService, URI); + return NS_NewChannel(getter_AddRefs(mScriptChannel), + mURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_SCRIPT, + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, + mIOService); + } + + nsIURI* ResolvedURI() { MOZ_ASSERT(mResolvedURI); return mResolvedURI; } + nsresult EnsureResolvedURI() { + BEGIN_ENSURE(ResolvedURI, ScriptChannel); + return mScriptChannel->GetURI(getter_AddRefs(mResolvedURI)); + } + + nsAutoCString& Key() { return *mKey; } + nsresult EnsureKey() { + ENSURE_DEPS(ResolvedURI); + mKey.emplace(); + return mResolvedURI->GetSpec(*mKey); + } + + MOZ_MUST_USE nsresult GetLocation(nsCString& aLocation) { + nsresult rv = EnsureURI(); + NS_ENSURE_SUCCESS(rv, rv); + return mURI->GetSpec(aLocation); + } + + private: + const nsACString& mLocation; + nsCOMPtr mIOService; + nsCOMPtr mURI; + nsCOMPtr mScriptChannel; + nsCOMPtr mResolvedURI; + Maybe mKey; // This is safe because we're MOZ_STACK_CLASS +}; + +#undef BEGIN_ENSURE +#undef ENSURE_DEPS +#undef ENSURE_DEP + +mozJSComponentLoader::~mozJSComponentLoader() +{ + if (mInitialized) { + NS_ERROR("'xpcom-shutdown-loaders' was not fired before cleaning up mozJSComponentLoader"); + UnloadModules(); + } + + sSelf = nullptr; +} + +mozJSComponentLoader* +mozJSComponentLoader::sSelf; + +NS_IMPL_ISUPPORTS(mozJSComponentLoader, + mozilla::ModuleLoader, + xpcIJSModuleLoader, + nsIObserver) + +nsresult +mozJSComponentLoader::ReallyInit() +{ + nsresult rv; + + mReuseLoaderGlobal = Preferences::GetBool("jsloader.reuseGlobal"); + + // XXXkhuey B2G child processes have some sort of preferences race that + // results in getting the wrong value. + // But we don't want that on Firefox Mulet as it break most Firefox JSMs... + // Also disable on debug builds to break js components that rely on this. +#if defined(MOZ_B2G) && !defined(MOZ_MULET) && !defined(DEBUG) + mReuseLoaderGlobal = true; +#endif + + nsCOMPtr secman = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + if (!secman) + return NS_ERROR_FAILURE; + + rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal)); + if (NS_FAILED(rv) || !mSystemPrincipal) + return NS_ERROR_FAILURE; + + nsCOMPtr obsSvc = + do_GetService(kObserverServiceContractID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = obsSvc->AddObserver(this, "xpcom-shutdown-loaders", false); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + return NS_OK; +} + +// For terrible compatibility reasons, we need to consider both the global +// lexical environment and the global of modules when searching for exported +// symbols. +static JSObject* +ResolveModuleObjectProperty(JSContext* aCx, HandleObject aModObj, const char* name) +{ + if (JS_HasExtensibleLexicalEnvironment(aModObj)) { + RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj)); + bool found; + if (!JS_HasOwnProperty(aCx, lexical, name, &found)) { + return nullptr; + } + if (found) { + return lexical; + } + } + return aModObj; +} + +const mozilla::Module* +mozJSComponentLoader::LoadModule(FileLocation& aFile) +{ + if (!NS_IsMainThread()) { + MOZ_ASSERT(false, "Don't use JS components off the main thread"); + return nullptr; + } + + nsCOMPtr file = aFile.GetBaseFile(); + + nsCString spec; + aFile.GetURIString(spec); + ComponentLoaderInfo info(spec); + nsresult rv = info.EnsureURI(); + NS_ENSURE_SUCCESS(rv, nullptr); + + if (!mInitialized) { + rv = ReallyInit(); + if (NS_FAILED(rv)) + return nullptr; + } + + ModuleEntry* mod; + if (mModules.Get(spec, &mod)) + return mod; + + dom::AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + + nsAutoPtr entry(new ModuleEntry(RootingContext::get(cx))); + RootedValue dummy(cx); + rv = ObjectForLocation(info, file, &entry->obj, &entry->thisObjectKey, + &entry->location, false, &dummy); + if (NS_FAILED(rv)) { + return nullptr; + } + + nsCOMPtr xpc = do_GetService(kXPConnectServiceContractID, + &rv); + if (NS_FAILED(rv)) + return nullptr; + + nsCOMPtr cm; + rv = NS_GetComponentManager(getter_AddRefs(cm)); + if (NS_FAILED(rv)) + return nullptr; + + JSAutoCompartment ac(cx, entry->obj); + RootedObject entryObj(cx, entry->obj); + + RootedObject NSGetFactoryHolder(cx, ResolveModuleObjectProperty(cx, entryObj, "NSGetFactory")); + RootedValue NSGetFactory_val(cx); + if (!NSGetFactoryHolder || + !JS_GetProperty(cx, NSGetFactoryHolder, "NSGetFactory", &NSGetFactory_val) || + NSGetFactory_val.isUndefined()) + { + return nullptr; + } + + if (JS_TypeOfValue(cx, NSGetFactory_val) != JSTYPE_FUNCTION) { + /* + * spec's encoding is ASCII unless it's zip file, otherwise it's + * random encoding. Latin1 variant is safe for random encoding. + */ + JS_ReportErrorLatin1(cx, "%s has NSGetFactory property that is not a function", + spec.get()); + return nullptr; + } + + RootedObject jsGetFactoryObj(cx); + if (!JS_ValueToObject(cx, NSGetFactory_val, &jsGetFactoryObj) || + !jsGetFactoryObj) { + /* XXX report error properly */ + return nullptr; + } + + rv = xpc->WrapJS(cx, jsGetFactoryObj, + NS_GET_IID(xpcIJSGetFactory), getter_AddRefs(entry->getfactoryobj)); + if (NS_FAILED(rv)) { + /* XXX report error properly */ +#ifdef DEBUG + fprintf(stderr, "mJCL: couldn't get nsIModule from jsval\n"); +#endif + return nullptr; + } + + // Cache this module for later + mModules.Put(spec, entry); + + // Set the location information for the new global, so that tools like + // about:memory may use that information + if (!mReuseLoaderGlobal) { + xpc::SetLocationForGlobal(entryObj, spec); + } + + // The hash owns the ModuleEntry now, forget about it + return entry.forget(); +} + +nsresult +mozJSComponentLoader::FindTargetObject(JSContext* aCx, + MutableHandleObject aTargetObject) +{ + aTargetObject.set(nullptr); + + RootedObject targetObject(aCx); + if (mReuseLoaderGlobal) { + JSFunction* fun = js::GetOutermostEnclosingFunctionOfScriptedCaller(aCx); + if (fun) { + JSObject* funParent = js::GetNearestEnclosingWithEnvironmentObjectForFunction(fun); + if (JS_GetClass(funParent) == &kFakeBackstagePassJSClass) + targetObject = funParent; + } + } + + // The above could fail, even if mReuseLoaderGlobal, if the scripted + // caller is not a component/JSM (it could be a DOM scope, for + // instance). + if (!targetObject) { + // Our targetObject is the caller's global object. Let's get it. + targetObject = CurrentGlobalOrNull(aCx); + } + + aTargetObject.set(targetObject); + return NS_OK; +} + +// This requires that the keys be strings and the values be pointers. +template +static size_t +SizeOfTableExcludingThis(const nsBaseHashtable& aTable, + MallocSizeOf aMallocSizeOf) +{ + size_t n = aTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = aTable.ConstIter(); !iter.Done(); iter.Next()) { + n += iter.Key().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += iter.Data()->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +size_t +mozJSComponentLoader::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + n += SizeOfTableExcludingThis(mModules, aMallocSizeOf); + n += SizeOfTableExcludingThis(mImports, aMallocSizeOf); + n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf); + return n; +} + +// Some stack based classes for cleaning up on early return +#ifdef HAVE_PR_MEMMAP +class FileAutoCloser +{ + public: + explicit FileAutoCloser(PRFileDesc* file) : mFile(file) {} + ~FileAutoCloser() { PR_Close(mFile); } + private: + PRFileDesc* mFile; +}; + +class FileMapAutoCloser +{ + public: + explicit FileMapAutoCloser(PRFileMap* map) : mMap(map) {} + ~FileMapAutoCloser() { PR_CloseFileMap(mMap); } + private: + PRFileMap* mMap; +}; +#else +class ANSIFileAutoCloser +{ + public: + explicit ANSIFileAutoCloser(FILE* file) : mFile(file) {} + ~ANSIFileAutoCloser() { fclose(mFile); } + private: + FILE* mFile; +}; +#endif + +JSObject* +mozJSComponentLoader::PrepareObjectForLocation(JSContext* aCx, + nsIFile* aComponentFile, + nsIURI* aURI, + bool aReuseLoaderGlobal, + bool* aRealFile) +{ + nsCOMPtr holder; + if (aReuseLoaderGlobal) { + holder = mLoaderGlobal; + } + + nsresult rv = NS_OK; + nsCOMPtr xpc = + do_GetService(kXPConnectServiceContractID, &rv); + NS_ENSURE_SUCCESS(rv, nullptr); + bool createdNewGlobal = false; + + if (!mLoaderGlobal) { + RefPtr backstagePass; + rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); + NS_ENSURE_SUCCESS(rv, nullptr); + + CompartmentOptions options; + + options.creationOptions() + .setZone(SystemZone) + .setAddonId(aReuseLoaderGlobal ? nullptr : MapURIToAddonID(aURI)); + + options.behaviors().setVersion(JSVERSION_LATEST); + + if (xpc::SharedMemoryEnabled()) + options.creationOptions().setSharedMemoryAndAtomicsEnabled(true); + + // Defer firing OnNewGlobalObject until after the __URI__ property has + // been defined so the JS debugger can tell what module the global is + // for + rv = xpc->InitClassesWithNewWrappedGlobal(aCx, + static_cast(backstagePass), + mSystemPrincipal, + nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK, + options, + getter_AddRefs(holder)); + NS_ENSURE_SUCCESS(rv, nullptr); + createdNewGlobal = true; + + RootedObject global(aCx, holder->GetJSObject()); + NS_ENSURE_TRUE(global, nullptr); + + backstagePass->SetGlobalObject(global); + + JSAutoCompartment ac(aCx, global); + if (!JS_DefineFunctions(aCx, global, gGlobalFun) || + !JS_DefineProfilingFunctions(aCx, global)) { + return nullptr; + } + + if (aReuseLoaderGlobal) { + mLoaderGlobal = holder; + } + } + + RootedObject obj(aCx, holder->GetJSObject()); + NS_ENSURE_TRUE(obj, nullptr); + + JSAutoCompartment ac(aCx, obj); + + if (aReuseLoaderGlobal) { + // If we're reusing the loader global, we don't actually use the + // global, but rather we use a different object as the 'this' object. + obj = JS_NewObject(aCx, &kFakeBackstagePassJSClass); + NS_ENSURE_TRUE(obj, nullptr); + } + + *aRealFile = false; + + // need to be extra careful checking for URIs pointing to files + // EnsureFile may not always get called, especially on resource URIs + // so we need to call GetFile to make sure this is a valid file + nsCOMPtr fileURL = do_QueryInterface(aURI, &rv); + nsCOMPtr testFile; + if (NS_SUCCEEDED(rv)) { + fileURL->GetFile(getter_AddRefs(testFile)); + } + + if (testFile) { + *aRealFile = true; + + if (XRE_IsParentProcess()) { + RootedObject locationObj(aCx); + + rv = xpc->WrapNative(aCx, obj, aComponentFile, + NS_GET_IID(nsIFile), + locationObj.address()); + NS_ENSURE_SUCCESS(rv, nullptr); + NS_ENSURE_TRUE(locationObj, nullptr); + + if (!JS_DefineProperty(aCx, obj, "__LOCATION__", locationObj, 0)) + return nullptr; + } + } + + nsAutoCString nativePath; + rv = aURI->GetSpec(nativePath); + NS_ENSURE_SUCCESS(rv, nullptr); + + // Expose the URI from which the script was imported through a special + // variable that we insert into the JSM. + RootedString exposedUri(aCx, JS_NewStringCopyN(aCx, nativePath.get(), nativePath.Length())); + NS_ENSURE_TRUE(exposedUri, nullptr); + + if (!JS_DefineProperty(aCx, obj, "__URI__", exposedUri, 0)) + return nullptr; + + if (createdNewGlobal) { + // AutoEntryScript required to invoke debugger hook, which is a + // Gecko-specific concept at present. + dom::AutoEntryScript aes(holder->GetJSObject(), + "component loader report global"); + RootedObject global(aes.cx(), holder->GetJSObject()); + JS_FireOnNewGlobalObject(aes.cx(), global); + } + + return obj; +} + +nsresult +mozJSComponentLoader::ObjectForLocation(ComponentLoaderInfo& aInfo, + nsIFile* aComponentFile, + MutableHandleObject aObject, + MutableHandleScript aTableScript, + char** aLocation, + bool aPropagateExceptions, + MutableHandleValue aException) +{ + MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); + + dom::AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + + bool realFile = false; + nsresult rv = aInfo.EnsureURI(); + NS_ENSURE_SUCCESS(rv, rv); + RootedObject obj(cx, PrepareObjectForLocation(cx, aComponentFile, aInfo.URI(), + mReuseLoaderGlobal, &realFile)); + NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE); + MOZ_ASSERT(JS_IsGlobalObject(obj) == !mReuseLoaderGlobal); + + JSAutoCompartment ac(cx, obj); + + RootedScript script(cx); + RootedFunction function(cx); + + nsAutoCString nativePath; + rv = aInfo.URI()->GetSpec(nativePath); + NS_ENSURE_SUCCESS(rv, rv); + + // Before compiling the script, first check to see if we have it in + // the startupcache. Note: as a rule, startupcache errors are not fatal + // to loading the script, since we can always slow-load. + + bool writeToCache = false; + StartupCache* cache = StartupCache::GetSingleton(); + + nsAutoCString cachePath(kJSCachePrefix); + rv = PathifyURI(aInfo.URI(), cachePath); + NS_ENSURE_SUCCESS(rv, rv); + + if (cache) { + if (!mReuseLoaderGlobal) { + rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script); + } else { + rv = ReadCachedFunction(cache, cachePath, cx, mSystemPrincipal, + function.address()); + } + + if (NS_SUCCEEDED(rv)) { + LOG(("Successfully loaded %s from startupcache\n", nativePath.get())); + } else { + // This is ok, it just means the script is not yet in the + // cache. Could mean that the cache was corrupted and got removed, + // but either way we're going to write this out. + writeToCache = true; + // ReadCachedScript and ReadCachedFunction may have set a pending + // exception. + JS_ClearPendingException(cx); + } + } + + if (!script && !function) { + // The script wasn't in the cache , so compile it now. + LOG(("Slow loading %s\n", nativePath.get())); + + // Use lazy source if both of these conditions hold: + // + // (1) mReuseLoaderGlobal is false. If mReuseLoaderGlobal is true, we + // can't do lazy source because we compile things as functions + // (rather than script), and lazy source isn't supported in that + // configuration. That's ok though, because we only do + // mReuseLoaderGlobal on b2g, where we invoke setDiscardSource(true) + // on the entire global. + // + // (2) We're using the startup cache. Non-lazy source + startup cache + // regresses installer size (due to source code stored in XDR + // encoded modules in omni.ja). Also, XDR decoding is relatively + // fast. Content processes don't use the startup cache, so we want + // them to use non-lazy source code to enable lazy parsing. + // See bug 1303754. + CompileOptions options(cx); + options.setNoScriptRval(mReuseLoaderGlobal ? false : true) + .setVersion(JSVERSION_LATEST) + .setFileAndLine(nativePath.get(), 1) + .setSourceIsLazy(!mReuseLoaderGlobal && !!cache); + + if (realFile) { +#ifdef HAVE_PR_MEMMAP + int64_t fileSize; + rv = aComponentFile->GetFileSize(&fileSize); + if (NS_FAILED(rv)) { + return rv; + } + + int64_t maxSize = UINT32_MAX; + if (fileSize > maxSize) { + NS_ERROR("file too large"); + return NS_ERROR_FAILURE; + } + + PRFileDesc* fileHandle; + rv = aComponentFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fileHandle); + if (NS_FAILED(rv)) { + return NS_ERROR_FILE_NOT_FOUND; + } + + // Make sure the file is closed, no matter how we return. + FileAutoCloser fileCloser(fileHandle); + + // We don't provide the file size here. If we did, PR_CreateFileMap + // would simply stat() the file to verify that the size we provided + // didn't require extending the file. We know that the file doesn't + // need to be extended, so skip the extra work by not providing the + // size. + PRFileMap* map = PR_CreateFileMap(fileHandle, 0, PR_PROT_READONLY); + if (!map) { + NS_ERROR("Failed to create file map"); + return NS_ERROR_FAILURE; + } + + // Make sure the file map is closed, no matter how we return. + FileMapAutoCloser mapCloser(map); + + uint32_t fileSize32 = fileSize; + + char* buf = static_cast(PR_MemMap(map, 0, fileSize32)); + if (!buf) { + NS_WARNING("Failed to map file"); + return NS_ERROR_FAILURE; + } + + if (!mReuseLoaderGlobal) { + Compile(cx, options, buf, fileSize32, &script); + } else { + // Note: exceptions will get handled further down; + // don't early return for them here. + AutoObjectVector envChain(cx); + if (envChain.append(obj)) { + CompileFunction(cx, envChain, + options, nullptr, 0, nullptr, + buf, fileSize32, &function); + } + } + + PR_MemUnmap(buf, fileSize32); + +#else /* HAVE_PR_MEMMAP */ + + /** + * No memmap implementation, so fall back to + * reading in the file + */ + + FILE* fileHandle; + rv = aComponentFile->OpenANSIFileDesc("r", &fileHandle); + if (NS_FAILED(rv)) { + return NS_ERROR_FILE_NOT_FOUND; + } + + // Ensure file fclose + ANSIFileAutoCloser fileCloser(fileHandle); + + int64_t len; + rv = aComponentFile->GetFileSize(&len); + if (NS_FAILED(rv) || len < 0) { + NS_WARNING("Failed to get file size"); + return NS_ERROR_FAILURE; + } + + char* buf = (char*) malloc(len * sizeof(char)); + if (!buf) { + return NS_ERROR_FAILURE; + } + + size_t rlen = fread(buf, 1, len, fileHandle); + if (rlen != (uint64_t)len) { + free(buf); + NS_WARNING("Failed to read file"); + return NS_ERROR_FAILURE; + } + + if (!mReuseLoaderGlobal) { + script = Compile(cx, options, buf, fileSize32); + } else { + // Note: exceptions will get handled further down; + // don't early return for them here. + AutoObjectVector envChain(cx); + if (envChain.append(obj)) { + CompileFunction(cx, envChain, + options, nullptr, 0, nullptr, + buf, fileSize32, &function); + } + } + + free(buf); + +#endif /* HAVE_PR_MEMMAP */ + } else { + rv = aInfo.EnsureScriptChannel(); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr scriptStream; + rv = NS_MaybeOpenChannelUsingOpen2(aInfo.ScriptChannel(), + getter_AddRefs(scriptStream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t len64; + uint32_t bytesRead; + + rv = scriptStream->Available(&len64); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(len64 < UINT32_MAX, NS_ERROR_FILE_TOO_BIG); + if (!len64) + return NS_ERROR_FAILURE; + uint32_t len = (uint32_t)len64; + + /* malloc an internal buf the size of the file */ + auto buf = MakeUniqueFallible(len + 1); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + + /* read the file in one swoop */ + rv = scriptStream->Read(buf.get(), len, &bytesRead); + if (bytesRead != len) + return NS_BASE_STREAM_OSERROR; + + buf[len] = '\0'; + + if (!mReuseLoaderGlobal) { + Compile(cx, options, buf.get(), bytesRead, &script); + } else { + // Note: exceptions will get handled further down; + // don't early return for them here. + AutoObjectVector envChain(cx); + if (envChain.append(obj)) { + CompileFunction(cx, envChain, + options, nullptr, 0, nullptr, + buf.get(), bytesRead, &function); + } + } + } + // Propagate the exception, if one exists. Also, don't leave the stale + // exception on this context. + if (!script && !function && aPropagateExceptions && + jsapi.HasException()) { + if (!jsapi.StealException(aException)) + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (!script && !function) { + return NS_ERROR_FAILURE; + } + + // We must have a script or a function (but not both!) here. We have a + // script when we're not reusing the loader global, and a function + // otherwise. + MOZ_ASSERT(!!script != !!function); + MOZ_ASSERT(!!script == JS_IsGlobalObject(obj)); + + if (writeToCache) { + // We successfully compiled the script, so cache it. + if (script) { + rv = WriteCachedScript(cache, cachePath, cx, mSystemPrincipal, + script); + } else { + rv = WriteCachedFunction(cache, cachePath, cx, mSystemPrincipal, + function); + } + + // Don't treat failure to write as fatal, since we might be working + // with a read-only cache. + if (NS_SUCCEEDED(rv)) { + LOG(("Successfully wrote to cache\n")); + } else { + LOG(("Failed to write to cache\n")); + } + } + + // Assign aObject here so that it's available to recursive imports. + // See bug 384168. + aObject.set(obj); + + RootedScript tableScript(cx, script); + if (!tableScript) { + tableScript = JS_GetFunctionScript(cx, function); + MOZ_ASSERT(tableScript); + } + + aTableScript.set(tableScript); + + + { // Scope for AutoEntryScript + + // We're going to run script via JS_ExecuteScript or + // JS_CallFunction, so we need an AutoEntryScript. + // This is Gecko-specific and not in any spec. + dom::AutoEntryScript aes(CurrentGlobalOrNull(cx), + "component loader load module"); + JSContext* aescx = aes.cx(); + bool ok; + if (script) { + ok = JS_ExecuteScript(aescx, script); + } else { + RootedValue rval(cx); + ok = JS_CallFunction(aescx, obj, function, + JS::HandleValueArray::empty(), &rval); + } + + if (!ok) { + if (aPropagateExceptions && aes.HasException()) { + // Ignore return value because we're returning an error code + // anyway. + Unused << aes.StealException(aException); + } + aObject.set(nullptr); + aTableScript.set(nullptr); + return NS_ERROR_FAILURE; + } + } + + /* Freed when we remove from the table. */ + *aLocation = ToNewCString(nativePath); + if (!*aLocation) { + aObject.set(nullptr); + aTableScript.set(nullptr); + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +void +mozJSComponentLoader::UnloadModules() +{ + mInitialized = false; + + if (mLoaderGlobal) { + MOZ_ASSERT(mReuseLoaderGlobal, "How did this happen?"); + + dom::AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + RootedObject global(cx, mLoaderGlobal->GetJSObject()); + if (global) { + JSAutoCompartment ac(cx, global); + if (JS_HasExtensibleLexicalEnvironment(global)) { + JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalEnvironment(global)); + } + JS_SetAllNonReservedSlotsToUndefined(cx, global); + } else { + NS_WARNING("Going to leak!"); + } + + mLoaderGlobal = nullptr; + } + + mInProgressImports.Clear(); + mImports.Clear(); + + for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) { + iter.Data()->Clear(); + iter.Remove(); + } +} + +NS_IMETHODIMP +mozJSComponentLoader::Import(const nsACString& registryLocation, + HandleValue targetValArg, + JSContext* cx, + uint8_t optionalArgc, + MutableHandleValue retval) +{ + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + + RootedValue targetVal(cx, targetValArg); + RootedObject targetObject(cx, nullptr); + if (optionalArgc) { + // The caller passed in the optional second argument. Get it. + if (targetVal.isObject()) { + // If we're passing in something like a content DOM window, chances + // are the caller expects the properties to end up on the object + // proper and not on the Xray holder. This is dubious, but can be used + // during testing. Given that dumb callers can already leak JSMs into + // content by passing a raw content JS object (where Xrays aren't + // possible), we aim for consistency here. Waive xray. + if (WrapperFactory::IsXrayWrapper(&targetVal.toObject()) && + !WrapperFactory::WaiveXrayAndWrap(cx, &targetVal)) + { + return NS_ERROR_FAILURE; + } + targetObject = &targetVal.toObject(); + } else if (!targetVal.isNull()) { + // If targetVal isNull(), we actually want to leave targetObject null. + // Not doing so breaks |make package|. + return ReportOnCallerUTF8(cx, ERROR_SCOPE_OBJ, + PromiseFlatCString(registryLocation).get()); + } + } else { + nsresult rv = FindTargetObject(cx, &targetObject); + NS_ENSURE_SUCCESS(rv, rv); + } + + Maybe ac; + if (targetObject) { + ac.emplace(cx, targetObject); + } + + RootedObject global(cx); + nsresult rv = ImportInto(registryLocation, targetObject, cx, &global); + + if (global) { + if (!JS_WrapObject(cx, &global)) { + NS_ERROR("can't wrap return value"); + return NS_ERROR_FAILURE; + } + + retval.setObject(*global); + } + return rv; +} + +NS_IMETHODIMP +mozJSComponentLoader::ImportInto(const nsACString& aLocation, + JSObject* aTargetObj, + nsAXPCNativeCallContext* cc, + JSObject** _retval) +{ + JSContext* callercx; + nsresult rv = cc->GetJSContext(&callercx); + NS_ENSURE_SUCCESS(rv, rv); + + RootedObject targetObject(callercx, aTargetObj); + RootedObject global(callercx); + rv = ImportInto(aLocation, targetObject, callercx, &global); + NS_ENSURE_SUCCESS(rv, rv); + *_retval = global; + return NS_OK; +} + +NS_IMETHODIMP +mozJSComponentLoader::IsModuleLoaded(const nsACString& aLocation, + bool* retval) +{ + MOZ_ASSERT(nsContentUtils::IsCallerChrome()); + + nsresult rv; + if (!mInitialized) { + rv = ReallyInit(); + NS_ENSURE_SUCCESS(rv, rv); + } + + ComponentLoaderInfo info(aLocation); + rv = info.EnsureKey(); + NS_ENSURE_SUCCESS(rv, rv); + + *retval = !!mImports.Get(info.Key()); + return NS_OK; +} + +static JSObject* +ResolveModuleObjectPropertyById(JSContext* aCx, HandleObject aModObj, HandleId id) +{ + if (JS_HasExtensibleLexicalEnvironment(aModObj)) { + RootedObject lexical(aCx, JS_ExtensibleLexicalEnvironment(aModObj)); + bool found; + if (!JS_HasOwnPropertyById(aCx, lexical, id, &found)) { + return nullptr; + } + if (found) { + return lexical; + } + } + return aModObj; +} + +nsresult +mozJSComponentLoader::ImportInto(const nsACString& aLocation, + HandleObject targetObj, + JSContext* callercx, + MutableHandleObject vp) +{ + vp.set(nullptr); + + nsresult rv; + if (!mInitialized) { + rv = ReallyInit(); + NS_ENSURE_SUCCESS(rv, rv); + } + + ComponentLoaderInfo info(aLocation); + rv = info.EnsureResolvedURI(); + NS_ENSURE_SUCCESS(rv, rv); + + // get the JAR if there is one + nsCOMPtr jarURI; + jarURI = do_QueryInterface(info.ResolvedURI(), &rv); + nsCOMPtr baseFileURL; + if (NS_SUCCEEDED(rv)) { + nsCOMPtr baseURI; + while (jarURI) { + jarURI->GetJARFile(getter_AddRefs(baseURI)); + jarURI = do_QueryInterface(baseURI, &rv); + } + baseFileURL = do_QueryInterface(baseURI, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + baseFileURL = do_QueryInterface(info.ResolvedURI(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr sourceFile; + rv = baseFileURL->GetFile(getter_AddRefs(sourceFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr sourceLocalFile; + sourceLocalFile = do_QueryInterface(sourceFile, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = info.EnsureKey(); + NS_ENSURE_SUCCESS(rv, rv); + + ModuleEntry* mod; + nsAutoPtr newEntry; + if (!mImports.Get(info.Key(), &mod) && !mInProgressImports.Get(info.Key(), &mod)) { + newEntry = new ModuleEntry(RootingContext::get(callercx)); + if (!newEntry) + return NS_ERROR_OUT_OF_MEMORY; + mInProgressImports.Put(info.Key(), newEntry); + + rv = info.EnsureURI(); + NS_ENSURE_SUCCESS(rv, rv); + RootedValue exception(callercx); + rv = ObjectForLocation(info, sourceLocalFile, &newEntry->obj, + &newEntry->thisObjectKey, + &newEntry->location, true, &exception); + + mInProgressImports.Remove(info.Key()); + + if (NS_FAILED(rv)) { + if (!exception.isUndefined()) { + // An exception was thrown during compilation. Propagate it + // out to our caller so they can report it. + if (!JS_WrapValue(callercx, &exception)) + return NS_ERROR_OUT_OF_MEMORY; + JS_SetPendingException(callercx, exception); + return NS_OK; + } + + // Something failed, but we don't know what it is, guess. + return NS_ERROR_FILE_NOT_FOUND; + } + + // Set the location information for the new global, so that tools like + // about:memory may use that information + if (!mReuseLoaderGlobal) { + xpc::SetLocationForGlobal(newEntry->obj, aLocation); + } + + mod = newEntry; + } + + MOZ_ASSERT(mod->obj, "Import table contains entry with no object"); + vp.set(mod->obj); + + if (targetObj) { + // cxhelper must be created before jsapi, so that jsapi is detroyed and + // pops any context it has pushed before we report to the caller context. + JSCLContextHelper cxhelper(callercx); + + // Even though we are calling JS_SetPropertyById on targetObj, we want + // to ensure that we never run script here, so we use an AutoJSAPI and + // not an AutoEntryScript. + dom::AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + JSAutoCompartment ac(cx, mod->obj); + + RootedValue symbols(cx); + RootedObject exportedSymbolsHolder(cx, ResolveModuleObjectProperty(cx, mod->obj, + "EXPORTED_SYMBOLS")); + if (!exportedSymbolsHolder || + !JS_GetProperty(cx, exportedSymbolsHolder, + "EXPORTED_SYMBOLS", &symbols)) { + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + return ReportOnCallerUTF8(cxhelper, ERROR_NOT_PRESENT, + location.get()); + } + + bool isArray; + if (!JS_IsArrayObject(cx, symbols, &isArray)) { + return NS_ERROR_FAILURE; + } + if (!isArray) { + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + return ReportOnCallerUTF8(cxhelper, ERROR_NOT_AN_ARRAY, + location.get()); + } + + RootedObject symbolsObj(cx, &symbols.toObject()); + + // Iterate over symbols array, installing symbols on targetObj: + + uint32_t symbolCount = 0; + if (!JS_GetArrayLength(cx, symbolsObj, &symbolCount)) { + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_ARRAY_LENGTH, + location.get()); + } + +#ifdef DEBUG + nsAutoCString logBuffer; +#endif + + RootedValue value(cx); + RootedId symbolId(cx); + RootedObject symbolHolder(cx); + for (uint32_t i = 0; i < symbolCount; ++i) { + if (!JS_GetElement(cx, symbolsObj, i, &value) || + !value.isString() || + !JS_ValueToId(cx, value, &symbolId)) { + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + return ReportOnCallerUTF8(cxhelper, ERROR_ARRAY_ELEMENT, + location.get(), i); + } + + symbolHolder = ResolveModuleObjectPropertyById(cx, mod->obj, symbolId); + if (!symbolHolder || + !JS_GetPropertyById(cx, symbolHolder, symbolId, &value)) { + JSAutoByteString bytes; + RootedString symbolStr(cx, JSID_TO_STRING(symbolId)); + if (!bytes.encodeUtf8(cx, symbolStr)) + return NS_ERROR_FAILURE; + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + return ReportOnCallerUTF8(cxhelper, ERROR_GETTING_SYMBOL, + location.get(), bytes.ptr()); + } + + JSAutoCompartment target_ac(cx, targetObj); + + if (!JS_WrapValue(cx, &value) || + !JS_SetPropertyById(cx, targetObj, symbolId, value)) { + JSAutoByteString bytes; + RootedString symbolStr(cx, JSID_TO_STRING(symbolId)); + if (!bytes.encodeUtf8(cx, symbolStr)) + return NS_ERROR_FAILURE; + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + return ReportOnCallerUTF8(cxhelper, ERROR_SETTING_SYMBOL, + location.get(), bytes.ptr()); + } +#ifdef DEBUG + if (i == 0) { + logBuffer.AssignLiteral("Installing symbols [ "); + } + JSAutoByteString bytes(cx, JSID_TO_STRING(symbolId)); + if (!!bytes) + logBuffer.Append(bytes.ptr()); + logBuffer.Append(' '); + if (i == symbolCount - 1) { + nsCString location; + rv = info.GetLocation(location); + NS_ENSURE_SUCCESS(rv, rv); + LOG(("%s] from %s\n", logBuffer.get(), location.get())); + } +#endif + } + } + + // Cache this module for later + if (newEntry) { + mImports.Put(info.Key(), newEntry); + newEntry.forget(); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozJSComponentLoader::Unload(const nsACString & aLocation) +{ + nsresult rv; + + if (!mInitialized) { + return NS_OK; + } + + MOZ_RELEASE_ASSERT(!mReuseLoaderGlobal, "Module unloading not supported when " + "compartment sharing is enabled"); + + ComponentLoaderInfo info(aLocation); + rv = info.EnsureKey(); + NS_ENSURE_SUCCESS(rv, rv); + ModuleEntry* mod; + if (mImports.Get(info.Key(), &mod)) { + mImports.Remove(info.Key()); + } + + return NS_OK; +} + +NS_IMETHODIMP +mozJSComponentLoader::Observe(nsISupports* subject, const char* topic, + const char16_t* data) +{ + if (!strcmp(topic, "xpcom-shutdown-loaders")) { + UnloadModules(); + } else { + NS_ERROR("Unexpected observer topic."); + } + + return NS_OK; +} + +size_t +mozJSComponentLoader::ModuleEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + size_t n = aMallocSizeOf(this); + n += aMallocSizeOf(location); + + return n; +} + +/* static */ already_AddRefed +mozJSComponentLoader::ModuleEntry::GetFactory(const mozilla::Module& module, + const mozilla::Module::CIDEntry& entry) +{ + const ModuleEntry& self = static_cast(module); + MOZ_ASSERT(self.getfactoryobj, "Handing out an uninitialized module?"); + + nsCOMPtr f; + nsresult rv = self.getfactoryobj->Get(*entry.cid, getter_AddRefs(f)); + if (NS_FAILED(rv)) + return nullptr; + + return f.forget(); +} + +//---------------------------------------------------------------------- + +JSCLContextHelper::JSCLContextHelper(JSContext* aCx) + : mContext(aCx) + , mBuf(nullptr) +{ +} + +JSCLContextHelper::~JSCLContextHelper() +{ + if (mBuf) { + JS_ReportErrorUTF8(mContext, "%s", mBuf); + JS_smprintf_free(mBuf); + } +} + +void +JSCLContextHelper::reportErrorAfterPop(char* buf) +{ + MOZ_ASSERT(!mBuf, "Already called reportErrorAfterPop"); + mBuf = buf; +} diff --git a/js/xpconnect/loader/mozJSComponentLoader.h b/js/xpconnect/loader/mozJSComponentLoader.h new file mode 100644 index 000000000..6ebe4a371 --- /dev/null +++ b/js/xpconnect/loader/mozJSComponentLoader.h @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozJSComponentLoader_h +#define mozJSComponentLoader_h + +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ModuleLoader.h" +#include "nsAutoPtr.h" +#include "nsISupports.h" +#include "nsIObserver.h" +#include "nsIURI.h" +#include "xpcIJSModuleLoader.h" +#include "nsClassHashtable.h" +#include "nsDataHashtable.h" +#include "jsapi.h" + +#include "xpcIJSGetFactory.h" + +class nsIFile; +class nsIPrincipal; +class nsIXPConnectJSObjectHolder; +class ComponentLoaderInfo; + +/* 6bd13476-1dd2-11b2-bbef-f0ccb5fa64b6 (thanks, mozbot) */ + +#define MOZJSCOMPONENTLOADER_CID \ + {0x6bd13476, 0x1dd2, 0x11b2, \ + { 0xbb, 0xef, 0xf0, 0xcc, 0xb5, 0xfa, 0x64, 0xb6 }} +#define MOZJSCOMPONENTLOADER_CONTRACTID "@mozilla.org/moz/jsloader;1" + +class mozJSComponentLoader : public mozilla::ModuleLoader, + public xpcIJSModuleLoader, + public nsIObserver +{ + public: + NS_DECL_ISUPPORTS + NS_DECL_XPCIJSMODULELOADER + NS_DECL_NSIOBSERVER + + mozJSComponentLoader(); + + // ModuleLoader + const mozilla::Module* LoadModule(mozilla::FileLocation& aFile) override; + + nsresult FindTargetObject(JSContext* aCx, + JS::MutableHandleObject aTargetObject); + + static mozJSComponentLoader* Get() { return sSelf; } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + protected: + virtual ~mozJSComponentLoader(); + + static mozJSComponentLoader* sSelf; + + nsresult ReallyInit(); + void UnloadModules(); + + JSObject* PrepareObjectForLocation(JSContext* aCx, + nsIFile* aComponentFile, + nsIURI* aComponent, + bool aReuseLoaderGlobal, + bool* aRealFile); + + nsresult ObjectForLocation(ComponentLoaderInfo& aInfo, + nsIFile* aComponentFile, + JS::MutableHandleObject aObject, + JS::MutableHandleScript aTableScript, + char** location, + bool aCatchException, + JS::MutableHandleValue aException); + + nsresult ImportInto(const nsACString& aLocation, + JS::HandleObject targetObj, + JSContext* callercx, + JS::MutableHandleObject vp); + + nsCOMPtr mCompMgr; + nsCOMPtr mSystemPrincipal; + nsCOMPtr mLoaderGlobal; + + class ModuleEntry : public mozilla::Module + { + public: + explicit ModuleEntry(JS::RootingContext* aRootingCx) + : mozilla::Module(), obj(aRootingCx), thisObjectKey(aRootingCx) + { + mVersion = mozilla::Module::kVersion; + mCIDs = nullptr; + mContractIDs = nullptr; + mCategoryEntries = nullptr; + getFactoryProc = GetFactory; + loadProc = nullptr; + unloadProc = nullptr; + + location = nullptr; + } + + ~ModuleEntry() { + Clear(); + } + + void Clear() { + getfactoryobj = nullptr; + + if (obj) { + mozilla::AutoJSContext cx; + JSAutoCompartment ac(cx, obj); + + if (JS_HasExtensibleLexicalEnvironment(obj)) { + JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalEnvironment(obj)); + } + JS_SetAllNonReservedSlotsToUndefined(cx, obj); + obj = nullptr; + thisObjectKey = nullptr; + } + + if (location) + free(location); + + obj = nullptr; + thisObjectKey = nullptr; + location = nullptr; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + static already_AddRefed GetFactory(const mozilla::Module& module, + const mozilla::Module::CIDEntry& entry); + + nsCOMPtr getfactoryobj; + JS::PersistentRootedObject obj; + JS::PersistentRootedScript thisObjectKey; + char* location; + }; + + friend class ModuleEntry; + + static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData, + mozilla::MallocSizeOf aMallocSizeOf, void* arg); + static size_t ClassEntrySizeOfExcludingThis(const nsACString& aKey, + const nsAutoPtr& aData, + mozilla::MallocSizeOf aMallocSizeOf, void* arg); + + // Modules are intentionally leaked, but still cleared. + nsDataHashtable mModules; + + nsClassHashtable mImports; + nsDataHashtable mInProgressImports; + + bool mInitialized; + bool mReuseLoaderGlobal; +}; + +#endif diff --git a/js/xpconnect/loader/mozJSLoaderUtils.cpp b/js/xpconnect/loader/mozJSLoaderUtils.cpp new file mode 100644 index 000000000..abc8aa3bb --- /dev/null +++ b/js/xpconnect/loader/mozJSLoaderUtils.cpp @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "mozilla/scache/StartupCache.h" + +#include "jsapi.h" +#include "jsfriendapi.h" + +#include "nsJSPrincipals.h" + +using namespace JS; +using namespace mozilla::scache; +using mozilla::UniquePtr; + +// We only serialize scripts with system principals. So we don't serialize the +// principals when writing a script. Instead, when reading it back, we set the +// principals to the system principals. +nsresult +ReadCachedScript(StartupCache* cache, nsACString& uri, JSContext* cx, + nsIPrincipal* systemPrincipal, MutableHandleScript scriptp) +{ + UniquePtr buf; + uint32_t len; + nsresult rv = cache->GetBuffer(PromiseFlatCString(uri).get(), &buf, &len); + if (NS_FAILED(rv)) + return rv; // don't warn since NOT_AVAILABLE is an ok error + + JS::TranscodeBuffer buffer; + buffer.replaceRawBuffer(reinterpret_cast(buf.release()), len); + JS::TranscodeResult code = JS::DecodeScript(cx, buffer, scriptp); + if (code == JS::TranscodeResult_Ok) + return NS_OK; + + if ((code & JS::TranscodeResult_Failure) != 0) + return NS_ERROR_FAILURE; + + MOZ_ASSERT((code & JS::TranscodeResult_Throw) != 0); + JS_ClearPendingException(cx); + return NS_ERROR_OUT_OF_MEMORY; +} + +nsresult +ReadCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx, + nsIPrincipal* systemPrincipal, JSFunction** functionp) +{ + // This doesn't actually work ... + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult +WriteCachedScript(StartupCache* cache, nsACString& uri, JSContext* cx, + nsIPrincipal* systemPrincipal, HandleScript script) +{ + MOZ_ASSERT(JS_GetScriptPrincipals(script) == nsJSPrincipals::get(systemPrincipal)); + + JS::TranscodeBuffer buffer; + JS::TranscodeResult code = JS::EncodeScript(cx, buffer, script); + if (code != JS::TranscodeResult_Ok) { + if ((code & JS::TranscodeResult_Failure) != 0) + return NS_ERROR_FAILURE; + MOZ_ASSERT((code & JS::TranscodeResult_Throw) != 0); + JS_ClearPendingException(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + size_t size = buffer.length(); + if (size > UINT32_MAX) + return NS_ERROR_FAILURE; + nsresult rv = cache->PutBuffer(PromiseFlatCString(uri).get(), + reinterpret_cast(buffer.begin()), + size); + return rv; +} + +nsresult +WriteCachedFunction(StartupCache* cache, nsACString& uri, JSContext* cx, + nsIPrincipal* systemPrincipal, JSFunction* function) +{ + // This doesn't actually work ... + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/js/xpconnect/loader/mozJSLoaderUtils.h b/js/xpconnect/loader/mozJSLoaderUtils.h new file mode 100644 index 000000000..9adf82bf6 --- /dev/null +++ b/js/xpconnect/loader/mozJSLoaderUtils.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozJSLoaderUtils_h +#define mozJSLoaderUtils_h + +#include "nsString.h" + +namespace mozilla { +namespace scache { +class StartupCache; +} // namespace scache +} // namespace mozilla + +nsresult +ReadCachedScript(mozilla::scache::StartupCache* cache, nsACString& uri, + JSContext* cx, nsIPrincipal* systemPrincipal, + JS::MutableHandleScript scriptp); + +nsresult +ReadCachedFunction(mozilla::scache::StartupCache* cache, nsACString& uri, + JSContext* cx, nsIPrincipal* systemPrincipal, + JSFunction** function); + +nsresult +WriteCachedScript(mozilla::scache::StartupCache* cache, nsACString& uri, + JSContext* cx, nsIPrincipal* systemPrincipal, + JS::HandleScript script); +nsresult +WriteCachedFunction(mozilla::scache::StartupCache* cache, nsACString& uri, + JSContext* cx, nsIPrincipal* systemPrincipal, + JSFunction* function); + +#endif /* mozJSLoaderUtils_h */ diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp new file mode 100644 index 000000000..824e9ab9e --- /dev/null +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -0,0 +1,929 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozJSSubScriptLoader.h" +#include "mozJSComponentLoader.h" +#include "mozJSLoaderUtils.h" + +#include "nsIURI.h" +#include "nsIIOService.h" +#include "nsIChannel.h" +#include "nsIInputStream.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIFileURL.h" +#include "nsScriptLoader.h" +#include "nsIScriptSecurityManager.h" +#include "nsThreadUtils.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "nsJSPrincipals.h" +#include "xpcprivate.h" // For xpc::OptionsBase +#include "jswrapper.h" + +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/scache/StartupCache.h" +#include "mozilla/scache/StartupCacheUtils.h" +#include "mozilla/Unused.h" +#include "nsContentUtils.h" +#include "nsStringGlue.h" +#include "nsCycleCollectionParticipant.h" + +using namespace mozilla::scache; +using namespace JS; +using namespace xpc; +using namespace mozilla; +using namespace mozilla::dom; + +class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase { +public: + explicit LoadSubScriptOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options) + , target(cx) + , charset(NullString()) + , ignoreCache(false) + , async(false) + { } + + virtual bool Parse() { + return ParseObject("target", &target) && + ParseString("charset", charset) && + ParseBoolean("ignoreCache", &ignoreCache) && + ParseBoolean("async", &async); + } + + RootedObject target; + nsString charset; + bool ignoreCache; + bool async; +}; + + +/* load() error msgs, XXX localize? */ +#define LOAD_ERROR_NOSERVICE "Error creating IO Service." +#define LOAD_ERROR_NOURI "Error creating URI (invalid URL scheme?)" +#define LOAD_ERROR_NOSCHEME "Failed to get URI scheme. This is bad." +#define LOAD_ERROR_URI_NOT_LOCAL "Trying to load a non-local URI." +#define LOAD_ERROR_NOSTREAM "Error opening input stream (invalid filename?)" +#define LOAD_ERROR_NOCONTENT "ContentLength not available (not a local URL?)" +#define LOAD_ERROR_BADCHARSET "Error converting to specified charset" +#define LOAD_ERROR_BADREAD "File Read Error." +#define LOAD_ERROR_READUNDERFLOW "File Read Error (underflow.)" +#define LOAD_ERROR_NOPRINCIPALS "Failed to get principals." +#define LOAD_ERROR_NOSPEC "Failed to get URI spec. This is bad." +#define LOAD_ERROR_CONTENTTOOBIG "ContentLength is too large" + +mozJSSubScriptLoader::mozJSSubScriptLoader() : mSystemPrincipal(nullptr) +{ +} + +mozJSSubScriptLoader::~mozJSSubScriptLoader() +{ + /* empty */ +} + +NS_IMPL_ISUPPORTS(mozJSSubScriptLoader, mozIJSSubScriptLoader) + +static void +ReportError(JSContext* cx, const char* msg) +{ + RootedValue exn(cx, JS::StringValue(JS_NewStringCopyZ(cx, msg))); + JS_SetPendingException(cx, exn); +} + +static void +ReportError(JSContext* cx, const char* origMsg, nsIURI* uri) +{ + if (!uri) { + ReportError(cx, origMsg); + return; + } + + nsAutoCString spec; + nsresult rv = uri->GetSpec(spec); + if (NS_FAILED(rv)) + spec.Assign("(unknown)"); + + nsAutoCString msg(origMsg); + msg.Append(": "); + msg.Append(spec); + ReportError(cx, msg.get()); +} + +bool +PrepareScript(nsIURI* uri, + JSContext* cx, + RootedObject& targetObj, + const char* uriStr, + const nsAString& charset, + const char* buf, + int64_t len, + bool reuseGlobal, + MutableHandleScript script, + MutableHandleFunction function) +{ + JS::CompileOptions options(cx); + options.setFileAndLine(uriStr, 1) + .setVersion(JSVERSION_LATEST); + if (!charset.IsVoid()) { + char16_t* scriptBuf = nullptr; + size_t scriptLength = 0; + + nsresult rv = + nsScriptLoader::ConvertToUTF16(nullptr, reinterpret_cast(buf), len, + charset, nullptr, scriptBuf, scriptLength); + + JS::SourceBufferHolder srcBuf(scriptBuf, scriptLength, + JS::SourceBufferHolder::GiveOwnership); + + if (NS_FAILED(rv)) { + ReportError(cx, LOAD_ERROR_BADCHARSET, uri); + return false; + } + + if (!reuseGlobal) { + if (JS_IsGlobalObject(targetObj)) { + return JS::Compile(cx, options, srcBuf, script); + } + return JS::CompileForNonSyntacticScope(cx, options, srcBuf, script); + } else { + AutoObjectVector envChain(cx); + if (!JS_IsGlobalObject(targetObj) && !envChain.append(targetObj)) { + return false; + } + return JS::CompileFunction(cx, envChain, options, nullptr, 0, nullptr, + srcBuf, function); + } + } else { + // We only use lazy source when no special encoding is specified because + // the lazy source loader doesn't know the encoding. + if (!reuseGlobal) { + options.setSourceIsLazy(true); + if (JS_IsGlobalObject(targetObj)) { + return JS::Compile(cx, options, buf, len, script); + } + return JS::CompileForNonSyntacticScope(cx, options, buf, len, script); + } else { + AutoObjectVector envChain(cx); + if (!JS_IsGlobalObject(targetObj) && !envChain.append(targetObj)) { + return false; + } + return JS::CompileFunction(cx, envChain, options, nullptr, 0, nullptr, + buf, len, function); + } + } +} + +bool +EvalScript(JSContext* cx, + RootedObject& target_obj, + MutableHandleValue retval, + nsIURI* uri, + bool cache, + RootedScript& script, + RootedFunction& function) +{ + if (function) { + script = JS_GetFunctionScript(cx, function); + } + + if (function) { + if (!JS_CallFunction(cx, target_obj, function, JS::HandleValueArray::empty(), retval)) { + return false; + } + } else { + if (JS_IsGlobalObject(target_obj)) { + if (!JS_ExecuteScript(cx, script, retval)) { + return false; + } + } else { + JS::AutoObjectVector envChain(cx); + if (!envChain.append(target_obj)) { + return false; + } + if (!JS_ExecuteScript(cx, envChain, script, retval)) { + return false; + } + } + } + + JSAutoCompartment rac(cx, target_obj); + if (!JS_WrapValue(cx, retval)) { + return false; + } + + if (cache && !!script) { + nsAutoCString cachePath; + JSVersion version = JS_GetVersion(cx); + cachePath.AppendPrintf("jssubloader/%d", version); + PathifyURI(uri, cachePath); + + nsCOMPtr secman = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + if (!secman) { + return false; + } + + nsCOMPtr principal; + nsresult rv = secman->GetSystemPrincipal(getter_AddRefs(principal)); + if (NS_FAILED(rv) || !principal) { + ReportError(cx, LOAD_ERROR_NOPRINCIPALS, uri); + return false; + } + + WriteCachedScript(StartupCache::GetSingleton(), + cachePath, cx, principal, script); + } + + return true; +} + +class AsyncScriptLoader : public nsIIncrementalStreamLoaderObserver +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER + + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader) + + AsyncScriptLoader(nsIChannel* aChannel, bool aReuseGlobal, + JSObject* aTargetObj, const nsAString& aCharset, + bool aCache, Promise* aPromise) + : mChannel(aChannel) + , mTargetObj(aTargetObj) + , mPromise(aPromise) + , mCharset(aCharset) + , mReuseGlobal(aReuseGlobal) + , mCache(aCache) + { + // Needed for the cycle collector to manage mTargetObj. + mozilla::HoldJSObjects(this); + } + +private: + virtual ~AsyncScriptLoader() { + mozilla::DropJSObjects(this); + } + + RefPtr mChannel; + Heap mTargetObj; + RefPtr mPromise; + nsString mCharset; + bool mReuseGlobal; + bool mCache; +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader) + NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise) + tmp->mTargetObj = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader) +NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader) + +class MOZ_STACK_CLASS AutoRejectPromise +{ +public: + AutoRejectPromise(AutoEntryScript& aAutoEntryScript, + Promise* aPromise, + nsIGlobalObject* aGlobalObject) + : mAutoEntryScript(aAutoEntryScript) + , mPromise(aPromise) + , mGlobalObject(aGlobalObject) {} + + ~AutoRejectPromise() { + if (mPromise) { + JSContext* cx = mAutoEntryScript.cx(); + RootedValue rejectionValue(cx, JS::UndefinedValue()); + if (mAutoEntryScript.HasException()) { + Unused << mAutoEntryScript.PeekException(&rejectionValue); + } + mPromise->MaybeReject(cx, rejectionValue); + } + } + + void ResolvePromise(HandleValue aResolveValue) { + mPromise->MaybeResolve(aResolveValue); + mPromise = nullptr; + } + +private: + AutoEntryScript& mAutoEntryScript; + RefPtr mPromise; + nsCOMPtr mGlobalObject; +}; + +NS_IMETHODIMP +AsyncScriptLoader::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + uint32_t aDataLength, + const uint8_t* aData, + uint32_t *aConsumedData) +{ + return NS_OK; +} + +NS_IMETHODIMP +AsyncScriptLoader::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + nsresult aStatus, + uint32_t aLength, + const uint8_t* aBuf) +{ + nsCOMPtr uri; + mChannel->GetURI(getter_AddRefs(uri)); + + nsCOMPtr globalObject = xpc::NativeGlobal(mTargetObj); + AutoEntryScript aes(globalObject, "async loadSubScript"); + AutoRejectPromise autoPromise(aes, mPromise, globalObject); + JSContext* cx = aes.cx(); + + if (NS_FAILED(aStatus)) { + ReportError(cx, "Unable to load script.", uri); + } + // Just notify that we are done with this load. + NS_ENSURE_SUCCESS(aStatus, NS_OK); + + if (aLength == 0) { + ReportError(cx, LOAD_ERROR_NOCONTENT, uri); + return NS_OK; + } + + if (aLength > INT32_MAX) { + ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri); + return NS_OK; + } + + RootedFunction function(cx); + RootedScript script(cx); + nsAutoCString spec; + nsresult rv = uri->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + RootedObject target_obj(cx, mTargetObj); + + if (!PrepareScript(uri, cx, target_obj, spec.get(), mCharset, + reinterpret_cast(aBuf), aLength, + mReuseGlobal, &script, &function)) + { + return NS_OK; + } + + JS::Rooted retval(cx); + if (EvalScript(cx, target_obj, &retval, uri, mCache, script, function)) { + autoPromise.ResolvePromise(retval); + } + + return NS_OK; +} + +nsresult +mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri, JSObject* targetObjArg, + const nsAString& charset, + nsIIOService* serv, bool reuseGlobal, + bool cache, MutableHandleValue retval) +{ + RootedObject target_obj(RootingCx(), targetObjArg); + + nsCOMPtr globalObject = xpc::NativeGlobal(target_obj); + ErrorResult result; + + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(globalObject))) { + return NS_ERROR_UNEXPECTED; + } + + RefPtr promise = Promise::Create(globalObject, result); + if (result.Failed()) { + return result.StealNSResult(); + } + + DebugOnly asJS = ToJSValue(jsapi.cx(), promise, retval); + MOZ_ASSERT(asJS, "Should not fail to convert the promise to a JS value"); + + // We create a channel and call SetContentType, to avoid expensive MIME type + // lookups (bug 632490). + nsCOMPtr channel; + nsresult rv; + rv = NS_NewChannel(getter_AddRefs(channel), + uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, + serv); + + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + channel->SetContentType(NS_LITERAL_CSTRING("application/javascript")); + + RefPtr loadObserver = + new AsyncScriptLoader(channel, + reuseGlobal, + target_obj, + charset, + cache, + promise); + + nsCOMPtr loader; + rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr listener = loader.get(); + return channel->AsyncOpen2(listener); +} + +bool +mozJSSubScriptLoader::ReadScript(nsIURI* uri, JSContext* cx, JSObject* targetObjArg, + const nsAString& charset, const char* uriStr, + nsIIOService* serv, nsIPrincipal* principal, + bool reuseGlobal, JS::MutableHandleScript script, + JS::MutableHandleFunction function) +{ + script.set(nullptr); + function.set(nullptr); + + RootedObject target_obj(cx, targetObjArg); + + // We create a channel and call SetContentType, to avoid expensive MIME type + // lookups (bug 632490). + nsCOMPtr chan; + nsCOMPtr instream; + nsresult rv; + rv = NS_NewChannel(getter_AddRefs(chan), + uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, + serv); + + if (NS_SUCCEEDED(rv)) { + chan->SetContentType(NS_LITERAL_CSTRING("application/javascript")); + rv = chan->Open2(getter_AddRefs(instream)); + } + + if (NS_FAILED(rv)) { + ReportError(cx, LOAD_ERROR_NOSTREAM, uri); + return false; + } + + int64_t len = -1; + + rv = chan->GetContentLength(&len); + if (NS_FAILED(rv) || len == -1) { + ReportError(cx, LOAD_ERROR_NOCONTENT, uri); + return false; + } + + if (len > INT32_MAX) { + ReportError(cx, LOAD_ERROR_CONTENTTOOBIG, uri); + return false; + } + + nsCString buf; + rv = NS_ReadInputStreamToString(instream, buf, len); + NS_ENSURE_SUCCESS(rv, false); + + return PrepareScript(uri, cx, target_obj, uriStr, charset, + buf.get(), len, + reuseGlobal, + script, function); +} + +NS_IMETHODIMP +mozJSSubScriptLoader::LoadSubScript(const nsAString& url, + HandleValue target, + const nsAString& charset, + JSContext* cx, + MutableHandleValue retval) +{ + /* + * Loads a local url and evals it into the current cx + * Synchronous (an async version would be cool too.) + * url: The url to load. Must be local so that it can be loaded + * synchronously. + * target_obj: Optional object to eval the script onto (defaults to context + * global) + * charset: Optional character set to use for reading + * returns: Whatever jsval the script pointed to by the url returns. + * Should ONLY (O N L Y !) be called from JavaScript code. + */ + LoadSubScriptOptions options(cx); + options.charset = charset; + options.target = target.isObject() ? &target.toObject() : nullptr; + return DoLoadSubScriptWithOptions(url, options, cx, retval); +} + + +NS_IMETHODIMP +mozJSSubScriptLoader::LoadSubScriptWithOptions(const nsAString& url, + HandleValue optionsVal, + JSContext* cx, + MutableHandleValue retval) +{ + if (!optionsVal.isObject()) + return NS_ERROR_INVALID_ARG; + LoadSubScriptOptions options(cx, &optionsVal.toObject()); + if (!options.Parse()) + return NS_ERROR_INVALID_ARG; + return DoLoadSubScriptWithOptions(url, options, cx, retval); +} + +nsresult +mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url, + LoadSubScriptOptions& options, + JSContext* cx, + MutableHandleValue retval) +{ + nsresult rv = NS_OK; + + /* set the system principal if it's not here already */ + if (!mSystemPrincipal) { + nsCOMPtr secman = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + if (!secman) + return NS_OK; + + rv = secman->GetSystemPrincipal(getter_AddRefs(mSystemPrincipal)); + if (NS_FAILED(rv) || !mSystemPrincipal) + return rv; + } + + RootedObject targetObj(cx); + mozJSComponentLoader* loader = mozJSComponentLoader::Get(); + rv = loader->FindTargetObject(cx, &targetObj); + NS_ENSURE_SUCCESS(rv, rv); + + // We base reusingGlobal off of what the loader told us, but we may not + // actually be using that object. + bool reusingGlobal = !JS_IsGlobalObject(targetObj); + + if (options.target) + targetObj = options.target; + + // Remember an object out of the calling compartment so that we + // can properly wrap the result later. + nsCOMPtr principal = mSystemPrincipal; + RootedObject result_obj(cx, targetObj); + targetObj = JS_FindCompilationScope(cx, targetObj); + if (!targetObj) + return NS_ERROR_FAILURE; + + if (targetObj != result_obj) + principal = GetObjectPrincipal(targetObj); + + /* load up the url. From here on, failures are reflected as ``custom'' + * js exceptions */ + nsCOMPtr uri; + nsAutoCString uriStr; + nsAutoCString scheme; + + // Figure out who's calling us + JS::AutoFilename filename; + if (!JS::DescribeScriptedCaller(cx, &filename)) { + // No scripted frame means we don't know who's calling, bail. + return NS_ERROR_FAILURE; + } + + JSAutoCompartment ac(cx, targetObj); + + // Suppress caching if we're compiling as content. + StartupCache* cache = (principal == mSystemPrincipal) + ? StartupCache::GetSingleton() + : nullptr; + nsCOMPtr serv = do_GetService(NS_IOSERVICE_CONTRACTID); + if (!serv) { + ReportError(cx, LOAD_ERROR_NOSERVICE); + return NS_OK; + } + + // Make sure to explicitly create the URI, since we'll need the + // canonicalized spec. + rv = NS_NewURI(getter_AddRefs(uri), NS_LossyConvertUTF16toASCII(url).get(), nullptr, serv); + if (NS_FAILED(rv)) { + ReportError(cx, LOAD_ERROR_NOURI); + return NS_OK; + } + + rv = uri->GetSpec(uriStr); + if (NS_FAILED(rv)) { + ReportError(cx, LOAD_ERROR_NOSPEC); + return NS_OK; + } + + rv = uri->GetScheme(scheme); + if (NS_FAILED(rv)) { + ReportError(cx, LOAD_ERROR_NOSCHEME, uri); + return NS_OK; + } + + if (!scheme.EqualsLiteral("chrome") && !scheme.EqualsLiteral("app")) { + // This might be a URI to a local file, though! + nsCOMPtr innerURI = NS_GetInnermostURI(uri); + nsCOMPtr fileURL = do_QueryInterface(innerURI); + if (!fileURL) { + ReportError(cx, LOAD_ERROR_URI_NOT_LOCAL, uri); + return NS_OK; + } + + // For file URIs prepend the filename with the filename of the + // calling script, and " -> ". See bug 418356. + nsAutoCString tmp(filename.get()); + tmp.AppendLiteral(" -> "); + tmp.Append(uriStr); + + uriStr = tmp; + } + + JSVersion version = JS_GetVersion(cx); + nsAutoCString cachePath; + cachePath.AppendPrintf("jssubloader/%d", version); + PathifyURI(uri, cachePath); + + RootedFunction function(cx); + RootedScript script(cx); + if (cache && !options.ignoreCache) { + rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script); + if (NS_FAILED(rv)) { + // ReadCachedScript may have set a pending exception. + JS_ClearPendingException(cx); + } + } + + // If we are doing an async load, trigger it and bail out. + if (!script && options.async) { + return ReadScriptAsync(uri, targetObj, options.charset, serv, + reusingGlobal, !!cache, retval); + } + + if (!script) { + if (!ReadScript(uri, cx, targetObj, options.charset, + static_cast(uriStr.get()), serv, + principal, reusingGlobal, &script, &function)) + { + return NS_OK; + } + } else { + cache = nullptr; + } + + Unused << EvalScript(cx, targetObj, retval, uri, !!cache, script, function); + return NS_OK; +} + +/** + * Let us compile scripts from a URI off the main thread. + */ + +class ScriptPrecompiler : public nsIIncrementalStreamLoaderObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER + + ScriptPrecompiler(nsIObserver* aObserver, + nsIPrincipal* aPrincipal, + nsIChannel* aChannel) + : mObserver(aObserver) + , mPrincipal(aPrincipal) + , mChannel(aChannel) + , mScriptBuf(nullptr) + , mScriptLength(0) + {} + + static void OffThreadCallback(void* aToken, void* aData); + + /* Sends the "done" notification back. Main thread only. */ + void SendObserverNotification(); + +private: + virtual ~ScriptPrecompiler() + { + if (mScriptBuf) { + js_free(mScriptBuf); + } + } + + RefPtr mObserver; + RefPtr mPrincipal; + RefPtr mChannel; + char16_t* mScriptBuf; + size_t mScriptLength; +}; + +NS_IMPL_ISUPPORTS(ScriptPrecompiler, nsIIncrementalStreamLoaderObserver); + +class NotifyPrecompilationCompleteRunnable : public Runnable +{ +public: + NS_DECL_NSIRUNNABLE + + explicit NotifyPrecompilationCompleteRunnable(ScriptPrecompiler* aPrecompiler) + : mPrecompiler(aPrecompiler) + , mToken(nullptr) + {} + + void SetToken(void* aToken) { + MOZ_ASSERT(aToken && !mToken); + mToken = aToken; + } + +protected: + RefPtr mPrecompiler; + void* mToken; +}; + +/* RAII helper class to send observer notifications */ +class AutoSendObserverNotification { +public: + explicit AutoSendObserverNotification(ScriptPrecompiler* aPrecompiler) + : mPrecompiler(aPrecompiler) + {} + + ~AutoSendObserverNotification() { + if (mPrecompiler) { + mPrecompiler->SendObserverNotification(); + } + } + + void Disarm() { + mPrecompiler = nullptr; + } + +private: + ScriptPrecompiler* mPrecompiler; +}; + +NS_IMETHODIMP +NotifyPrecompilationCompleteRunnable::Run(void) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mPrecompiler); + + AutoSendObserverNotification notifier(mPrecompiler); + + if (mToken) { + JSContext* cx = XPCJSContext::Get()->Context(); + NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE); + JS::CancelOffThreadScript(cx, mToken); + } + + return NS_OK; +} + +NS_IMETHODIMP +ScriptPrecompiler::OnIncrementalData(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + uint32_t aDataLength, + const uint8_t* aData, + uint32_t *aConsumedData) +{ + return NS_OK; +} + +NS_IMETHODIMP +ScriptPrecompiler::OnStreamComplete(nsIIncrementalStreamLoader* aLoader, + nsISupports* aContext, + nsresult aStatus, + uint32_t aLength, + const uint8_t* aString) +{ + AutoSendObserverNotification notifier(this); + + // Just notify that we are done with this load. + NS_ENSURE_SUCCESS(aStatus, NS_OK); + + // Convert data to char16_t* and prepare to call CompileOffThread. + nsAutoString hintCharset; + nsresult rv = + nsScriptLoader::ConvertToUTF16(mChannel, aString, aLength, + hintCharset, nullptr, + mScriptBuf, mScriptLength); + + NS_ENSURE_SUCCESS(rv, NS_OK); + + // Our goal is to cache persistently the compiled script and to avoid quota + // checks. Since the caching mechanism decide the persistence type based on + // the principal, we create a new global with the app's principal. + // We then enter its compartment to compile with its principal. + AutoSafeJSContext cx; + RootedValue v(cx); + SandboxOptions sandboxOptions; + sandboxOptions.sandboxName.AssignASCII("asm.js precompilation"); + sandboxOptions.invisibleToDebugger = true; + sandboxOptions.discardSource = true; + rv = CreateSandboxObject(cx, &v, mPrincipal, sandboxOptions); + NS_ENSURE_SUCCESS(rv, NS_OK); + + JSAutoCompartment ac(cx, js::UncheckedUnwrap(&v.toObject())); + + JS::CompileOptions options(cx, JSVERSION_DEFAULT); + options.forceAsync = true; + options.installedFile = true; + + nsCOMPtr uri; + mChannel->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + uri->GetSpec(spec); + options.setFile(spec.get()); + + if (!JS::CanCompileOffThread(cx, options, mScriptLength)) { + NS_WARNING("Can't compile script off thread!"); + return NS_OK; + } + + RefPtr runnable = + new NotifyPrecompilationCompleteRunnable(this); + + if (!JS::CompileOffThread(cx, options, + mScriptBuf, mScriptLength, + OffThreadCallback, + static_cast(runnable))) { + NS_WARNING("Failed to compile script off thread!"); + return NS_OK; + } + + Unused << runnable.forget(); + notifier.Disarm(); + + return NS_OK; +} + +/* static */ +void +ScriptPrecompiler::OffThreadCallback(void* aToken, void* aData) +{ + RefPtr runnable = + dont_AddRef(static_cast(aData)); + runnable->SetToken(aToken); + + NS_DispatchToMainThread(runnable); +} + +void +ScriptPrecompiler::SendObserverNotification() +{ + MOZ_ASSERT(mChannel && mObserver); + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr uri; + mChannel->GetURI(getter_AddRefs(uri)); + mObserver->Observe(uri, "script-precompiled", nullptr); +} + +NS_IMETHODIMP +mozJSSubScriptLoader::PrecompileScript(nsIURI* aURI, + nsIPrincipal* aPrincipal, + nsIObserver* aObserver) +{ + nsCOMPtr channel; + nsresult rv = NS_NewChannel(getter_AddRefs(channel), + aURI, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr loadObserver = + new ScriptPrecompiler(aObserver, aPrincipal, channel); + + nsCOMPtr loader; + rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr listener = loader.get(); + rv = channel->AsyncOpen2(listener); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.h b/js/xpconnect/loader/mozJSSubScriptLoader.h new file mode 100644 index 000000000..15ed5a14c --- /dev/null +++ b/js/xpconnect/loader/mozJSSubScriptLoader.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsCOMPtr.h" +#include "mozIJSSubScriptLoader.h" + +class nsIPrincipal; +class nsIURI; +class LoadSubScriptOptions; + +#define MOZ_JSSUBSCRIPTLOADER_CID \ +{ /* 829814d6-1dd2-11b2-8e08-82fa0a339b00 */ \ + 0x929814d6, \ + 0x1dd2, \ + 0x11b2, \ + {0x8e, 0x08, 0x82, 0xfa, 0x0a, 0x33, 0x9b, 0x00} \ +} + +class nsIIOService; + +class mozJSSubScriptLoader : public mozIJSSubScriptLoader +{ +public: + mozJSSubScriptLoader(); + + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_MOZIJSSUBSCRIPTLOADER + +private: + virtual ~mozJSSubScriptLoader(); + + bool ReadScript(nsIURI* uri, JSContext* cx, JSObject* target_obj, + const nsAString& charset, const char* uriStr, + nsIIOService* serv, nsIPrincipal* principal, + bool reuseGlobal, JS::MutableHandleScript script, + JS::MutableHandleFunction function); + + nsresult ReadScriptAsync(nsIURI* uri, JSObject* target_obj, + const nsAString& charset, + nsIIOService* serv, bool reuseGlobal, + bool cache, JS::MutableHandleValue retval); + + nsresult DoLoadSubScriptWithOptions(const nsAString& url, + LoadSubScriptOptions& options, + JSContext* cx, + JS::MutableHandle retval); + + nsCOMPtr mSystemPrincipal; +}; diff --git a/js/xpconnect/moz.build b/js/xpconnect/moz.build new file mode 100644 index 000000000..65bf8da0e --- /dev/null +++ b/js/xpconnect/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +with Files('**'): + BUG_COMPONENT = ('Core', 'XPConnect') + +DIRS += ['public', 'idl', 'wrappers', 'loader', 'src'] +DIRS += ['shell'] +TEST_DIRS += ['tests'] + +if CONFIG['MOZ_XPCTOOLS']: + DIRS += ['tools'] + diff --git a/js/xpconnect/public/moz.build b/js/xpconnect/public/moz.build new file mode 100644 index 000000000..b6650e395 --- /dev/null +++ b/js/xpconnect/public/moz.build @@ -0,0 +1,13 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'nsAXPCNativeCallContext.h', + 'nsTArrayHelpers.h', + 'xpc_make_class.h', + 'xpc_map_end.h', +] + diff --git a/js/xpconnect/public/nsAXPCNativeCallContext.h b/js/xpconnect/public/nsAXPCNativeCallContext.h new file mode 100644 index 000000000..aeafe7ebe --- /dev/null +++ b/js/xpconnect/public/nsAXPCNativeCallContext.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsAXPCNativeCallContext_h__ +#define nsAXPCNativeCallContext_h__ + +/** +* A native call context is allocated on the stack when XPConnect calls a +* native method. Holding a pointer to this object beyond the currently +* executing stack frame is not permitted. +*/ +class nsAXPCNativeCallContext +{ +public: + NS_IMETHOD GetCallee(nsISupports** aResult) = 0; + NS_IMETHOD GetCalleeMethodIndex(uint16_t* aResult) = 0; + NS_IMETHOD GetJSContext(JSContext** aResult) = 0; + NS_IMETHOD GetArgc(uint32_t* aResult) = 0; + NS_IMETHOD GetArgvPtr(JS::Value** aResult) = 0; + + // Methods added since mozilla 0.6.... + + NS_IMETHOD GetCalleeInterface(nsIInterfaceInfo** aResult) = 0; + NS_IMETHOD GetCalleeClassInfo(nsIClassInfo** aResult) = 0; + + NS_IMETHOD GetPreviousCallContext(nsAXPCNativeCallContext** aResult) = 0; +}; + +#endif diff --git a/js/xpconnect/public/nsTArrayHelpers.h b/js/xpconnect/public/nsTArrayHelpers.h new file mode 100644 index 000000000..aa0ce8198 --- /dev/null +++ b/js/xpconnect/public/nsTArrayHelpers.h @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __NSTARRAYHELPERS_H__ +#define __NSTARRAYHELPERS_H__ + +#include "jsapi.h" +#include "nsContentUtils.h" +#include "nsTArray.h" + +template +inline nsresult +nsTArrayToJSArray(JSContext* aCx, const nsTArray& aSourceArray, + JS::MutableHandle aResultArray) +{ + MOZ_ASSERT(aCx); + + JS::Rooted arrayObj(aCx, + JS_NewArrayObject(aCx, aSourceArray.Length())); + if (!arrayObj) { + NS_WARNING("JS_NewArrayObject failed!"); + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t index = 0; index < aSourceArray.Length(); index++) { + nsCOMPtr obj; + nsresult rv = aSourceArray[index]->QueryInterface(NS_GET_IID(nsISupports), getter_AddRefs(obj)); + NS_ENSURE_SUCCESS(rv, rv); + + JS::RootedValue wrappedVal(aCx); + rv = nsContentUtils::WrapNative(aCx, obj, &wrappedVal); + NS_ENSURE_SUCCESS(rv, rv); + + if (!JS_DefineElement(aCx, arrayObj, index, wrappedVal, JSPROP_ENUMERATE)) { + NS_WARNING("JS_DefineElement failed!"); + return NS_ERROR_FAILURE; + } + } + + if (!JS_FreezeObject(aCx, arrayObj)) { + NS_WARNING("JS_FreezeObject failed!"); + return NS_ERROR_FAILURE; + } + + aResultArray.set(arrayObj); + return NS_OK; +} + +template <> +inline nsresult +nsTArrayToJSArray(JSContext* aCx, + const nsTArray& aSourceArray, + JS::MutableHandle aResultArray) +{ + MOZ_ASSERT(aCx); + + JS::Rooted arrayObj(aCx, + JS_NewArrayObject(aCx, aSourceArray.Length())); + if (!arrayObj) { + NS_WARNING("JS_NewArrayObject failed!"); + return NS_ERROR_OUT_OF_MEMORY; + } + + JS::Rooted s(aCx); + for (uint32_t index = 0; index < aSourceArray.Length(); index++) { + s = JS_NewUCStringCopyN(aCx, aSourceArray[index].BeginReading(), + aSourceArray[index].Length()); + + if(!s) { + NS_WARNING("Memory allocation error!"); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!JS_DefineElement(aCx, arrayObj, index, s, JSPROP_ENUMERATE)) { + NS_WARNING("JS_DefineElement failed!"); + return NS_ERROR_FAILURE; + } + } + + if (!JS_FreezeObject(aCx, arrayObj)) { + NS_WARNING("JS_FreezeObject failed!"); + return NS_ERROR_FAILURE; + } + + aResultArray.set(arrayObj); + return NS_OK; +} + +#endif /* __NSTARRAYHELPERS_H__ */ diff --git a/js/xpconnect/public/xpc_make_class.h b/js/xpconnect/public/xpc_make_class.h new file mode 100644 index 000000000..f612d3056 --- /dev/null +++ b/js/xpconnect/public/xpc_make_class.h @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef xpc_make_class_h +#define xpc_make_class_h + +// This file should be used to create js::Class instances for nsIXPCScriptable +// instances. This includes any file that uses xpc_map_end.h. + +#include "xpcpublic.h" +#include "mozilla/dom/DOMJSClass.h" + +bool +XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::HandleValue v); +bool +XPC_WN_CannotModifyPropertyStub(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::HandleValue v); + +bool +XPC_WN_MaybeResolvingDeletePropertyStub(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, + JS::ObjectOpResult& result); +bool +XPC_WN_CantDeletePropertyStub(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::ObjectOpResult& result); + +bool +XPC_WN_Helper_GetProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp); + +bool +XPC_WN_Helper_SetProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp, + JS::ObjectOpResult& result); +bool +XPC_WN_MaybeResolvingSetPropertyStub(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::MutableHandleValue vp, + JS::ObjectOpResult& result); +bool +XPC_WN_CannotModifySetPropertyStub(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, JS::MutableHandleValue vp, + JS::ObjectOpResult& result); + +bool +XPC_WN_Helper_Enumerate(JSContext* cx, JS::HandleObject obj); +bool +XPC_WN_Shared_Enumerate(JSContext* cx, JS::HandleObject obj); + +bool +XPC_WN_Helper_Resolve(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + bool* resolvedp); + +void +XPC_WN_Helper_Finalize(js::FreeOp* fop, JSObject* obj); +void +XPC_WN_NoHelper_Finalize(js::FreeOp* fop, JSObject* obj); + +bool +XPC_WN_Helper_Call(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +XPC_WN_Helper_HasInstance(JSContext* cx, JS::HandleObject obj, + JS::MutableHandleValue valp, bool* bp); + +bool +XPC_WN_Helper_Construct(JSContext* cx, unsigned argc, JS::Value* vp); + +void +XPCWrappedNative_Trace(JSTracer* trc, JSObject* obj); + +extern const js::ClassExtension XPC_WN_JSClassExtension; + +extern const js::ObjectOps XPC_WN_ObjectOpsWithEnumerate; + +#define XPC_MAKE_CLASS_OPS(_flags) { \ + /* addProperty */ \ + ((_flags) & nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY) \ + ? nullptr \ + : ((_flags) & nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE) \ + ? XPC_WN_MaybeResolvingPropertyStub \ + : XPC_WN_CannotModifyPropertyStub, \ + \ + /* delProperty */ \ + ((_flags) & nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY) \ + ? nullptr \ + : ((_flags) & nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE) \ + ? XPC_WN_MaybeResolvingDeletePropertyStub \ + : XPC_WN_CantDeletePropertyStub, \ + \ + /* getProperty */ \ + ((_flags) & nsIXPCScriptable::WANT_GETPROPERTY) \ + ? XPC_WN_Helper_GetProperty \ + : nullptr, \ + \ + /* setProperty */ \ + ((_flags) & nsIXPCScriptable::WANT_SETPROPERTY) \ + ? XPC_WN_Helper_SetProperty \ + : ((_flags) & nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY) \ + ? nullptr \ + : ((_flags) & nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE) \ + ? XPC_WN_MaybeResolvingSetPropertyStub \ + : XPC_WN_CannotModifySetPropertyStub, \ + \ + /* enumerate */ \ + ((_flags) & nsIXPCScriptable::WANT_NEWENUMERATE) \ + ? nullptr /* We will use oOps->enumerate set below in this case */ \ + : ((_flags) & nsIXPCScriptable::WANT_ENUMERATE) \ + ? XPC_WN_Helper_Enumerate \ + : XPC_WN_Shared_Enumerate, \ + \ + /* resolve */ \ + /* We have to figure out resolve strategy at call time */ \ + XPC_WN_Helper_Resolve, \ + \ + /* mayResolve */ \ + nullptr, \ + \ + /* finalize */ \ + ((_flags) & nsIXPCScriptable::WANT_FINALIZE) \ + ? XPC_WN_Helper_Finalize \ + : XPC_WN_NoHelper_Finalize, \ + \ + /* call */ \ + ((_flags) & nsIXPCScriptable::WANT_CALL) \ + ? XPC_WN_Helper_Call \ + : nullptr, \ + \ + /* hasInstance */ \ + ((_flags) & nsIXPCScriptable::WANT_HASINSTANCE) \ + ? XPC_WN_Helper_HasInstance \ + : nullptr, \ + \ + /* construct */ \ + ((_flags) & nsIXPCScriptable::WANT_CONSTRUCT) \ + ? XPC_WN_Helper_Construct \ + : nullptr, \ + \ + /* trace */ \ + ((_flags) & nsIXPCScriptable::IS_GLOBAL_OBJECT) \ + ? JS_GlobalObjectTraceHook \ + : XPCWrappedNative_Trace, \ +} + +#define XPC_MAKE_CLASS(_name, _flags, _classOps) { \ + /* name */ \ + _name, \ + \ + /* flags */ \ + XPC_WRAPPER_FLAGS | \ + JSCLASS_PRIVATE_IS_NSISUPPORTS | \ + JSCLASS_IS_WRAPPED_NATIVE | \ + (((_flags) & nsIXPCScriptable::IS_GLOBAL_OBJECT) \ + ? XPCONNECT_GLOBAL_FLAGS \ + : 0), \ + \ + /* cOps */ \ + _classOps, \ + \ + /* spec */ \ + nullptr, \ + \ + /* ext */ \ + &XPC_WN_JSClassExtension, \ + \ + /* oOps */ \ + ((_flags) & nsIXPCScriptable::WANT_NEWENUMERATE) \ + ? &XPC_WN_ObjectOpsWithEnumerate \ + : nullptr, \ +} + +#endif diff --git a/js/xpconnect/public/xpc_map_end.h b/js/xpconnect/public/xpc_map_end.h new file mode 100644 index 000000000..dfb7b10d4 --- /dev/null +++ b/js/xpconnect/public/xpc_map_end.h @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// If you include this file you must also include xpc_make_class.h at the top +// of the file doing the including. + +#ifndef XPC_MAP_CLASSNAME +#error "Must #define XPC_MAP_CLASSNAME before #including xpc_map_end.h" +#endif + +#ifndef XPC_MAP_QUOTED_CLASSNAME +#error "Must #define XPC_MAP_QUOTED_CLASSNAME before #including xpc_map_end.h" +#endif + +#include "js/Id.h" + +/**************************************************************/ + +NS_IMETHODIMP XPC_MAP_CLASSNAME::GetClassName(char * *aClassName) +{ + static const char sName[] = XPC_MAP_QUOTED_CLASSNAME; + *aClassName = (char*) nsMemory::Clone(sName, sizeof(sName)); + return NS_OK; +} + +/**************************************************************/ + +// virtual +uint32_t +XPC_MAP_CLASSNAME::GetScriptableFlags() +{ + return +#ifdef XPC_MAP_WANT_PRECREATE + nsIXPCScriptable::WANT_PRECREATE | +#endif +#ifdef XPC_MAP_WANT_ADDPROPERTY + nsIXPCScriptable::WANT_ADDPROPERTY | +#endif +#ifdef XPC_MAP_WANT_GETPROPERTY + nsIXPCScriptable::WANT_GETPROPERTY | +#endif +#ifdef XPC_MAP_WANT_SETPROPERTY + nsIXPCScriptable::WANT_SETPROPERTY | +#endif +#ifdef XPC_MAP_WANT_ENUMERATE + nsIXPCScriptable::WANT_ENUMERATE | +#endif +#ifdef XPC_MAP_WANT_NEWENUMERATE + nsIXPCScriptable::WANT_NEWENUMERATE | +#endif +#ifdef XPC_MAP_WANT_RESOLVE + nsIXPCScriptable::WANT_RESOLVE | +#endif +#ifdef XPC_MAP_WANT_FINALIZE + nsIXPCScriptable::WANT_FINALIZE | +#endif +#ifdef XPC_MAP_WANT_CALL + nsIXPCScriptable::WANT_CALL | +#endif +#ifdef XPC_MAP_WANT_CONSTRUCT + nsIXPCScriptable::WANT_CONSTRUCT | +#endif +#ifdef XPC_MAP_WANT_HASINSTANCE + nsIXPCScriptable::WANT_HASINSTANCE | +#endif +#ifdef XPC_MAP_FLAGS + XPC_MAP_FLAGS | +#endif + 0; +} + +// virtual +const js::Class* +XPC_MAP_CLASSNAME::GetClass() +{ + static const js::ClassOps classOps = + XPC_MAKE_CLASS_OPS(GetScriptableFlags()); + static const js::Class klass = + XPC_MAKE_CLASS(XPC_MAP_QUOTED_CLASSNAME, GetScriptableFlags(), + &classOps); + return &klass; +} + +/**************************************************************/ + +#ifndef XPC_MAP_WANT_PRECREATE +NS_IMETHODIMP XPC_MAP_CLASSNAME::PreCreate(nsISupports* nativeObj, JSContext * cx, JSObject * globalObj, JSObject * *parentObj) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_ADDPROPERTY +NS_IMETHODIMP XPC_MAP_CLASSNAME::AddProperty(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, jsid id, JS::HandleValue val, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_GETPROPERTY +NS_IMETHODIMP XPC_MAP_CLASSNAME::GetProperty(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, jsid id, JS::Value * vp, bool* _retval) + {NS_WARNING("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_SETPROPERTY +NS_IMETHODIMP XPC_MAP_CLASSNAME::SetProperty(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, jsid id, JS::Value * vp, bool* _retval) + {NS_WARNING("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_NEWENUMERATE +NS_IMETHODIMP XPC_MAP_CLASSNAME::NewEnumerate(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, JS::AutoIdVector& properties, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_ENUMERATE +NS_IMETHODIMP XPC_MAP_CLASSNAME::Enumerate(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_RESOLVE +NS_IMETHODIMP XPC_MAP_CLASSNAME::Resolve(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, jsid id, bool* resolvedp, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_FINALIZE +NS_IMETHODIMP XPC_MAP_CLASSNAME::Finalize(nsIXPConnectWrappedNative* wrapper, JSFreeOp * fop, JSObject * obj) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_CALL +NS_IMETHODIMP XPC_MAP_CLASSNAME::Call(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, const JS::CallArgs& args, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_CONSTRUCT +NS_IMETHODIMP XPC_MAP_CLASSNAME::Construct(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, const JS::CallArgs& args, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_HASINSTANCE +NS_IMETHODIMP XPC_MAP_CLASSNAME::HasInstance(nsIXPConnectWrappedNative* wrapper, JSContext * cx, JSObject * obj, JS::HandleValue val, bool* bp, bool* _retval) + {NS_ERROR("never called"); return NS_ERROR_NOT_IMPLEMENTED;} +#endif + +#ifndef XPC_MAP_WANT_POST_CREATE_PROTOTYPE +NS_IMETHODIMP XPC_MAP_CLASSNAME::PostCreatePrototype(JSContext* cx, JSObject* proto) + {return NS_OK;} +#endif + +/**************************************************************/ + +#undef XPC_MAP_CLASSNAME +#undef XPC_MAP_QUOTED_CLASSNAME + +#ifdef XPC_MAP_WANT_PRECREATE +#undef XPC_MAP_WANT_PRECREATE +#endif + +#ifdef XPC_MAP_WANT_ADDPROPERTY +#undef XPC_MAP_WANT_ADDPROPERTY +#endif + +#ifdef XPC_MAP_WANT_GETPROPERTY +#undef XPC_MAP_WANT_GETPROPERTY +#endif + +#ifdef XPC_MAP_WANT_SETPROPERTY +#undef XPC_MAP_WANT_SETPROPERTY +#endif + +#ifdef XPC_MAP_WANT_ENUMERATE +#undef XPC_MAP_WANT_ENUMERATE +#endif + +#ifdef XPC_MAP_WANT_NEWENUMERATE +#undef XPC_MAP_WANT_NEWENUMERATE +#endif + +#ifdef XPC_MAP_WANT_RESOLVE +#undef XPC_MAP_WANT_RESOLVE +#endif + +#ifdef XPC_MAP_WANT_FINALIZE +#undef XPC_MAP_WANT_FINALIZE +#endif + +#ifdef XPC_MAP_WANT_CALL +#undef XPC_MAP_WANT_CALL +#endif + +#ifdef XPC_MAP_WANT_CONSTRUCT +#undef XPC_MAP_WANT_CONSTRUCT +#endif + +#ifdef XPC_MAP_WANT_HASINSTANCE +#undef XPC_MAP_WANT_HASINSTANCE +#endif + +#ifdef XPC_MAP_WANT_POST_CREATE_PROTOTYPE +#undef XPC_MAP_WANT_POST_CREATE_PROTOTYPE +#endif + +#ifdef XPC_MAP_FLAGS +#undef XPC_MAP_FLAGS +#endif diff --git a/js/xpconnect/shell/Makefile.in b/js/xpconnect/shell/Makefile.in new file mode 100644 index 000000000..e5aaf7b44 --- /dev/null +++ b/js/xpconnect/shell/Makefile.in @@ -0,0 +1,6 @@ +# +# 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/. + +NSDISTMODE = copy diff --git a/js/xpconnect/shell/moz.build b/js/xpconnect/shell/moz.build new file mode 100644 index 000000000..ecc796f7f --- /dev/null +++ b/js/xpconnect/shell/moz.build @@ -0,0 +1,60 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +GeckoProgram('xpcshell', linkage='dependent') + +if CONFIG['COMPILE_ENVIRONMENT']: + SDK_FILES.bin += [ + '!xpcshell%s' % CONFIG['BIN_SUFFIX'], + ] + +SOURCES += [ + 'xpcshell.cpp', +] + +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + SOURCES += [ + 'xpcshellMacUtils.mm', + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +LOCAL_INCLUDES += [ + '/toolkit/xre', +] + +if CONFIG['_MSC_VER']: + # Always enter a Windows program through wmain, whether or not we're + # a console application. + WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup'] + +# DELAYLOAD_DLLS in this block ensure that the DLL blocklist initializes +if CONFIG['OS_ARCH'] == 'WINNT': + RCINCLUDE = 'xpcshell.rc' + + if CONFIG['MOZ_SANDBOX']: + # For sandbox includes and the include dependencies those have + LOCAL_INCLUDES += [ + '/security/sandbox/chromium', + '/security/sandbox/chromium-shim', + ] + + USE_LIBS += [ + 'sandbox_s', + ] + + DELAYLOAD_DLLS += [ + 'winmm.dll', + 'user32.dll', + ] + + DELAYLOAD_DLLS += [ + 'xul.dll', + ] + +CFLAGS += CONFIG['TK_CFLAGS'] +CXXFLAGS += CONFIG['TK_CFLAGS'] +OS_LIBS += CONFIG['TK_LIBS'] diff --git a/js/xpconnect/shell/xpcshell.cpp b/js/xpconnect/shell/xpcshell.cpp new file mode 100644 index 000000000..ba979bc69 --- /dev/null +++ b/js/xpconnect/shell/xpcshell.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* XPConnect JavaScript interactive shell. */ + +#include + +#include "mozilla/WindowsDllBlocklist.h" + +#include "nsXULAppAPI.h" +#ifdef XP_MACOSX +#include "xpcshellMacUtils.h" +#endif +#ifdef XP_WIN +#include +#include + +// we want a wmain entry point +#define XRE_DONT_PROTECT_DLL_LOAD +#define XRE_WANT_ENVIRON +#include "nsWindowsWMain.cpp" +#ifdef MOZ_SANDBOX +#include "mozilla/sandboxing/SandboxInitialization.h" +#endif +#endif + +#ifdef MOZ_WIDGET_GTK +#include +#endif + +int +main(int argc, char** argv, char** envp) +{ +#ifdef MOZ_WIDGET_GTK + // A default display may or may not be required for xpcshell tests, and so + // is not created here. Instead we set the command line args, which is a + // fairly cheap operation. + gtk_parse_args(&argc, &argv); +#endif + +#ifdef XP_MACOSX + InitAutoreleasePool(); +#endif + + // unbuffer stdout so that output is in the correct order; note that stderr + // is unbuffered by default + setbuf(stdout, 0); + +#ifdef HAS_DLL_BLOCKLIST + DllBlocklist_Initialize(); +#endif + + XREShellData shellData; +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + shellData.sandboxBrokerServices = + mozilla::sandboxing::GetInitializedBrokerServices(); +#endif + + int result = XRE_XPCShellMain(argc, argv, envp, &shellData); + +#ifdef XP_MACOSX + FinishAutoreleasePool(); +#endif + + return result; +} diff --git a/js/xpconnect/shell/xpcshell.exe.manifest b/js/xpconnect/shell/xpcshell.exe.manifest new file mode 100644 index 000000000..db2aa0861 --- /dev/null +++ b/js/xpconnect/shell/xpcshell.exe.manifest @@ -0,0 +1,31 @@ + + + + + + +XPConnect Shell + + + + + + + + + + + + + + + + + diff --git a/js/xpconnect/shell/xpcshell.rc b/js/xpconnect/shell/xpcshell.rc new file mode 100644 index 000000000..1b6bec6d4 --- /dev/null +++ b/js/xpconnect/shell/xpcshell.rc @@ -0,0 +1,6 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +1 RT_MANIFEST "xpcshell.exe.manifest" diff --git a/js/xpconnect/shell/xpcshellMacUtils.h b/js/xpconnect/shell/xpcshellMacUtils.h new file mode 100644 index 000000000..61d9030a9 --- /dev/null +++ b/js/xpconnect/shell/xpcshellMacUtils.h @@ -0,0 +1,9 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Functions to setup and release the Mac memory pool +void InitAutoreleasePool(); +void FinishAutoreleasePool(); diff --git a/js/xpconnect/shell/xpcshellMacUtils.mm b/js/xpconnect/shell/xpcshellMacUtils.mm new file mode 100644 index 000000000..e0e4b04c8 --- /dev/null +++ b/js/xpconnect/shell/xpcshellMacUtils.mm @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=2 sw=2 et tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include + +static NSAutoreleasePool *pool = NULL; + +void InitAutoreleasePool() +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +void FinishAutoreleasePool() +{ + [pool release]; +} diff --git a/js/xpconnect/src/BackstagePass.h b/js/xpconnect/src/BackstagePass.h new file mode 100644 index 000000000..be7d15cdf --- /dev/null +++ b/js/xpconnect/src/BackstagePass.h @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef BackstagePass_h__ +#define BackstagePass_h__ + +#include "nsISupports.h" +#include "nsWeakReference.h" +#include "nsIGlobalObject.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIXPCScriptable.h" + +#include "js/HeapAPI.h" + +class XPCWrappedNative; + +class BackstagePass : public nsIGlobalObject, + public nsIScriptObjectPrincipal, + public nsIXPCScriptable, + public nsIClassInfo, + public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + virtual nsIPrincipal* GetPrincipal() override { + return mPrincipal; + } + + virtual JSObject* GetGlobalJSObject() override; + + void ForgetGlobalObject() { + mWrapper = nullptr; + } + + void SetGlobalObject(JSObject* global); + + explicit BackstagePass(nsIPrincipal* prin) : + mPrincipal(prin) + { + } + +private: + virtual ~BackstagePass() { } + + nsCOMPtr mPrincipal; + XPCWrappedNative* mWrapper; +}; + +nsresult +NS_NewBackstagePass(BackstagePass** ret); + +#endif // BackstagePass_h__ diff --git a/js/xpconnect/src/ExportHelpers.cpp b/js/xpconnect/src/ExportHelpers.cpp new file mode 100644 index 000000000..3dbf83e3b --- /dev/null +++ b/js/xpconnect/src/ExportHelpers.cpp @@ -0,0 +1,491 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "WrapperFactory.h" +#include "AccessCheck.h" +#include "jsfriendapi.h" +#include "jswrapper.h" +#include "js/Proxy.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FileListBinding.h" +#include "mozilla/dom/StructuredCloneHolder.h" +#include "nsGlobalWindow.h" +#include "nsJSUtils.h" +#include "nsIDOMFileList.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +namespace xpc { + +bool +IsReflector(JSObject* obj) +{ + obj = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + if (!obj) + return false; + return IS_WN_REFLECTOR(obj) || dom::IsDOMObject(obj); +} + +enum StackScopedCloneTags { + SCTAG_BASE = JS_SCTAG_USER_MIN, + SCTAG_REFLECTOR, + SCTAG_BLOB, + SCTAG_FUNCTION, +}; + +// The HTML5 structured cloning algorithm includes a few DOM objects, notably +// FileList. That wouldn't in itself be a reason to support them here, +// but we've historically supported them for Cu.cloneInto (where we didn't support +// other reflectors), so we need to continue to do so in the wrapReflectors == false +// case to maintain compatibility. +// +// FileList clones are supposed to give brand new objects, rather than +// cross-compartment wrappers. For this, our current implementation relies on the +// fact that these objects are implemented with XPConnect and have one reflector +// per scope. +bool IsFileList(JSObject* obj) +{ + return IS_INSTANCE_OF(FileList, obj); +} + +class MOZ_STACK_CLASS StackScopedCloneData + : public StructuredCloneHolderBase +{ +public: + StackScopedCloneData(JSContext* aCx, StackScopedCloneOptions* aOptions) + : mOptions(aOptions) + , mReflectors(aCx) + , mFunctions(aCx) + {} + + ~StackScopedCloneData() + { + Clear(); + } + + JSObject* CustomReadHandler(JSContext* aCx, + JSStructuredCloneReader* aReader, + uint32_t aTag, + uint32_t aData) + { + if (aTag == SCTAG_REFLECTOR) { + MOZ_ASSERT(!aData); + + size_t idx; + if (!JS_ReadBytes(aReader, &idx, sizeof(size_t))) + return nullptr; + + RootedObject reflector(aCx, mReflectors[idx]); + MOZ_ASSERT(reflector, "No object pointer?"); + MOZ_ASSERT(IsReflector(reflector), "Object pointer must be a reflector!"); + + if (!JS_WrapObject(aCx, &reflector)) + return nullptr; + + return reflector; + } + + if (aTag == SCTAG_FUNCTION) { + MOZ_ASSERT(aData < mFunctions.length()); + + RootedValue functionValue(aCx); + RootedObject obj(aCx, mFunctions[aData]); + + if (!JS_WrapObject(aCx, &obj)) + return nullptr; + + FunctionForwarderOptions forwarderOptions; + if (!xpc::NewFunctionForwarder(aCx, JSID_VOIDHANDLE, obj, forwarderOptions, + &functionValue)) + { + return nullptr; + } + + return &functionValue.toObject(); + } + + if (aTag == SCTAG_BLOB) { + MOZ_ASSERT(!aData); + + size_t idx; + if (!JS_ReadBytes(aReader, &idx, sizeof(size_t))) { + return nullptr; + } + + nsIGlobalObject* global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)); + MOZ_ASSERT(global); + + // RefPtr needs to go out of scope before toObjectOrNull() is called because + // otherwise the static analysis thinks it can gc the JSObject via the stack. + JS::Rooted val(aCx); + { + RefPtr blob = Blob::Create(global, mBlobImpls[idx]); + if (!ToJSValue(aCx, blob, &val)) { + return nullptr; + } + } + + return val.toObjectOrNull(); + } + + MOZ_ASSERT_UNREACHABLE("Encountered garbage in the clone stream!"); + return nullptr; + } + + bool CustomWriteHandler(JSContext* aCx, + JSStructuredCloneWriter* aWriter, + JS::Handle aObj) + { + { + JS::Rooted obj(aCx, aObj); + Blob* blob = nullptr; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, &obj, blob))) { + BlobImpl* blobImpl = blob->Impl(); + MOZ_ASSERT(blobImpl); + + if (!mBlobImpls.AppendElement(blobImpl)) + return false; + + size_t idx = mBlobImpls.Length() - 1; + return JS_WriteUint32Pair(aWriter, SCTAG_BLOB, 0) && + JS_WriteBytes(aWriter, &idx, sizeof(size_t)); + } + } + + if ((mOptions->wrapReflectors && IsReflector(aObj)) || + IsFileList(aObj)) + { + if (!mReflectors.append(aObj)) + return false; + + size_t idx = mReflectors.length() - 1; + if (!JS_WriteUint32Pair(aWriter, SCTAG_REFLECTOR, 0)) + return false; + if (!JS_WriteBytes(aWriter, &idx, sizeof(size_t))) + return false; + return true; + } + + if (JS::IsCallable(aObj)) { + if (mOptions->cloneFunctions) { + if (!mFunctions.append(aObj)) + return false; + return JS_WriteUint32Pair(aWriter, SCTAG_FUNCTION, mFunctions.length() - 1); + } else { + JS_ReportErrorASCII(aCx, "Permission denied to pass a Function via structured clone"); + return false; + } + } + + JS_ReportErrorASCII(aCx, "Encountered unsupported value type writing stack-scoped structured clone"); + return false; + } + + StackScopedCloneOptions* mOptions; + AutoObjectVector mReflectors; + AutoObjectVector mFunctions; + nsTArray> mBlobImpls; +}; + +/* + * General-purpose structured-cloning utility for cases where the structured + * clone buffer is only used in stack-scope (that is to say, the buffer does + * not escape from this function). The stack-scoping allows us to pass + * references to various JSObjects directly in certain situations without + * worrying about lifetime issues. + * + * This function assumes that |cx| is already entered the compartment we want + * to clone to, and that |val| may not be same-compartment with cx. When the + * function returns, |val| is set to the result of the clone. + */ +bool +StackScopedClone(JSContext* cx, StackScopedCloneOptions& options, + MutableHandleValue val) +{ + StackScopedCloneData data(cx, &options); + { + // For parsing val we have to enter its compartment. + // (unless it's a primitive) + Maybe ac; + if (val.isObject()) { + ac.emplace(cx, &val.toObject()); + } else if (val.isString() && !JS_WrapValue(cx, val)) { + return false; + } + + if (!data.Write(cx, val)) + return false; + } + + // Now recreate the clones in the target compartment. + if (!data.Read(cx, val)) + return false; + + // Deep-freeze if requested. + if (options.deepFreeze && val.isObject()) { + RootedObject obj(cx, &val.toObject()); + if (!JS_DeepFreezeObject(cx, obj)) + return false; + } + + return true; +} + +// Note - This function mirrors the logic of CheckPassToChrome in +// ChromeObjectWrapper.cpp. +static bool +CheckSameOriginArg(JSContext* cx, FunctionForwarderOptions& options, HandleValue v) +{ + // Consumers can explicitly opt out of this security check. This is used in + // the web console to allow the utility functions to accept cross-origin Windows. + if (options.allowCrossOriginArguments) + return true; + + // Primitives are fine. + if (!v.isObject()) + return true; + RootedObject obj(cx, &v.toObject()); + MOZ_ASSERT(js::GetObjectCompartment(obj) != js::GetContextCompartment(cx), + "This should be invoked after entering the compartment but before " + "wrapping the values"); + + // Non-wrappers are fine. + if (!js::IsWrapper(obj)) + return true; + + // Wrappers leading back to the scope of the exported function are fine. + if (js::GetObjectCompartment(js::UncheckedUnwrap(obj)) == js::GetContextCompartment(cx)) + return true; + + // Same-origin wrappers are fine. + if (AccessCheck::wrapperSubsumes(obj)) + return true; + + // Badness. + JS_ReportErrorASCII(cx, "Permission denied to pass object to exported function"); + return false; +} + +static bool +FunctionForwarder(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Grab the options from the reserved slot. + RootedObject optionsObj(cx, &js::GetFunctionNativeReserved(&args.callee(), 1).toObject()); + FunctionForwarderOptions options(cx, optionsObj); + if (!options.Parse()) + return false; + + // Grab and unwrap the underlying callable. + RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0)); + RootedObject unwrappedFun(cx, js::UncheckedUnwrap(&v.toObject())); + + RootedObject thisObj(cx, args.isConstructing() ? nullptr : JS_THIS_OBJECT(cx, vp)); + { + // We manually implement the contents of CrossCompartmentWrapper::call + // here, because certain function wrappers (notably content->nsEP) are + // not callable. + JSAutoCompartment ac(cx, unwrappedFun); + + RootedValue thisVal(cx, ObjectOrNullValue(thisObj)); + if (!CheckSameOriginArg(cx, options, thisVal) || !JS_WrapObject(cx, &thisObj)) + return false; + + for (size_t n = 0; n < args.length(); ++n) { + if (!CheckSameOriginArg(cx, options, args[n]) || !JS_WrapValue(cx, args[n])) + return false; + } + + RootedValue fval(cx, ObjectValue(*unwrappedFun)); + if (args.isConstructing()) { + RootedObject obj(cx); + if (!JS::Construct(cx, fval, args, &obj)) + return false; + args.rval().setObject(*obj); + } else { + if (!JS_CallFunctionValue(cx, thisObj, fval, args, args.rval())) + return false; + } + } + + // Rewrap the return value into our compartment. + return JS_WrapValue(cx, args.rval()); +} + +bool +NewFunctionForwarder(JSContext* cx, HandleId idArg, HandleObject callable, + FunctionForwarderOptions& options, MutableHandleValue vp) +{ + RootedId id(cx, idArg); + if (id == JSID_VOIDHANDLE) + id = GetJSIDByIndex(cx, XPCJSContext::IDX_EMPTYSTRING); + + // We have no way of knowing whether the underlying function wants to be a + // constructor or not, so we just mark all forwarders as constructors, and + // let the underlying function throw for construct calls if it wants. + JSFunction* fun = js::NewFunctionByIdWithReserved(cx, FunctionForwarder, + 0, JSFUN_CONSTRUCTOR, id); + if (!fun) + return false; + + // Stash the callable in slot 0. + AssertSameCompartment(cx, callable); + RootedObject funobj(cx, JS_GetFunctionObject(fun)); + js::SetFunctionNativeReserved(funobj, 0, ObjectValue(*callable)); + + // Stash the options in slot 1. + RootedObject optionsObj(cx, options.ToJSObject(cx)); + if (!optionsObj) + return false; + js::SetFunctionNativeReserved(funobj, 1, ObjectValue(*optionsObj)); + + vp.setObject(*funobj); + return true; +} + +bool +ExportFunction(JSContext* cx, HandleValue vfunction, HandleValue vscope, HandleValue voptions, + MutableHandleValue rval) +{ + bool hasOptions = !voptions.isUndefined(); + if (!vscope.isObject() || !vfunction.isObject() || (hasOptions && !voptions.isObject())) { + JS_ReportErrorASCII(cx, "Invalid argument"); + return false; + } + + RootedObject funObj(cx, &vfunction.toObject()); + RootedObject targetScope(cx, &vscope.toObject()); + ExportFunctionOptions options(cx, hasOptions ? &voptions.toObject() : nullptr); + if (hasOptions && !options.Parse()) + return false; + + // Restrictions: + // * We must subsume the scope we are exporting to. + // * We must subsume the function being exported, because the function + // forwarder manually circumvents security wrapper CALL restrictions. + targetScope = js::CheckedUnwrap(targetScope); + funObj = js::CheckedUnwrap(funObj); + if (!targetScope || !funObj) { + JS_ReportErrorASCII(cx, "Permission denied to export function into scope"); + return false; + } + + if (js::IsScriptedProxy(targetScope)) { + JS_ReportErrorASCII(cx, "Defining property on proxy object is not allowed"); + return false; + } + + { + // We need to operate in the target scope from here on, let's enter + // its compartment. + JSAutoCompartment ac(cx, targetScope); + + // Unwrapping to see if we have a callable. + funObj = UncheckedUnwrap(funObj); + if (!JS::IsCallable(funObj)) { + JS_ReportErrorASCII(cx, "First argument must be a function"); + return false; + } + + RootedId id(cx, options.defineAs); + if (JSID_IS_VOID(id)) { + // If there wasn't any function name specified, + // copy the name from the function being imported. + JSFunction* fun = JS_GetObjectFunction(funObj); + RootedString funName(cx, JS_GetFunctionId(fun)); + if (!funName) + funName = JS_AtomizeAndPinString(cx, ""); + + if (!JS_StringToId(cx, funName, &id)) + return false; + } + MOZ_ASSERT(JSID_IS_STRING(id)); + + // The function forwarder will live in the target compartment. Since + // this function will be referenced from its private slot, to avoid a + // GC hazard, we must wrap it to the same compartment. + if (!JS_WrapObject(cx, &funObj)) + return false; + + // And now, let's create the forwarder function in the target compartment + // for the function the be exported. + FunctionForwarderOptions forwarderOptions; + forwarderOptions.allowCrossOriginArguments = options.allowCrossOriginArguments; + if (!NewFunctionForwarder(cx, id, funObj, forwarderOptions, rval)) { + JS_ReportErrorASCII(cx, "Exporting function failed"); + return false; + } + + // We have the forwarder function in the target compartment. If + // defineAs was set, we also need to define it as a property on + // the target. + if (!JSID_IS_VOID(options.defineAs)) { + if (!JS_DefinePropertyById(cx, targetScope, id, rval, + JSPROP_ENUMERATE, + JS_STUBGETTER, JS_STUBSETTER)) { + return false; + } + } + } + + // Finally we have to re-wrap the exported function back to the caller compartment. + if (!JS_WrapValue(cx, rval)) + return false; + + return true; +} + +bool +CreateObjectIn(JSContext* cx, HandleValue vobj, CreateObjectInOptions& options, + MutableHandleValue rval) +{ + if (!vobj.isObject()) { + JS_ReportErrorASCII(cx, "Expected an object as the target scope"); + return false; + } + + RootedObject scope(cx, js::CheckedUnwrap(&vobj.toObject())); + if (!scope) { + JS_ReportErrorASCII(cx, "Permission denied to create object in the target scope"); + return false; + } + + bool define = !JSID_IS_VOID(options.defineAs); + + if (define && js::IsScriptedProxy(scope)) { + JS_ReportErrorASCII(cx, "Defining property on proxy object is not allowed"); + return false; + } + + RootedObject obj(cx); + { + JSAutoCompartment ac(cx, scope); + obj = JS_NewPlainObject(cx); + if (!obj) + return false; + + if (define) { + if (!JS_DefinePropertyById(cx, scope, options.defineAs, obj, + JSPROP_ENUMERATE, + JS_STUBGETTER, JS_STUBSETTER)) + return false; + } + } + + rval.setObject(*obj); + if (!WrapperFactory::WaiveXrayAndWrap(cx, rval)) + return false; + + return true; +} + +} /* namespace xpc */ diff --git a/js/xpconnect/src/README b/js/xpconnect/src/README new file mode 100644 index 000000000..260eed6bc --- /dev/null +++ b/js/xpconnect/src/README @@ -0,0 +1,3 @@ + +see http://www.mozilla.org/scriptable + diff --git a/js/xpconnect/src/Sandbox.cpp b/js/xpconnect/src/Sandbox.cpp new file mode 100644 index 000000000..120772ed2 --- /dev/null +++ b/js/xpconnect/src/Sandbox.cpp @@ -0,0 +1,1966 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * The Components.Sandbox object. + */ + +#include "AccessCheck.h" +#include "jsfriendapi.h" +#include "js/Proxy.h" +#include "js/StructuredClone.h" +#include "nsContentUtils.h" +#include "nsGlobalWindow.h" +#include "nsIScriptContext.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIURI.h" +#include "nsJSUtils.h" +#include "nsNetUtil.h" +#include "nsNullPrincipal.h" +#include "nsPrincipal.h" +#include "WrapperFactory.h" +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "XPCWrapper.h" +#include "XrayWrapper.h" +#include "Crypto.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/BlobBinding.h" +#include "mozilla/dom/cache/CacheStorage.h" +#include "mozilla/dom/CSSBinding.h" +#include "mozilla/dom/DirectoryBinding.h" +#include "mozilla/dom/IndexedDatabaseManager.h" +#include "mozilla/dom/Fetch.h" +#include "mozilla/dom/FileBinding.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/ResponseBinding.h" +#ifdef MOZ_WEBRTC +#include "mozilla/dom/RTCIdentityProviderRegistrar.h" +#endif +#include "mozilla/dom/FileReaderBinding.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/TextDecoderBinding.h" +#include "mozilla/dom/TextEncoderBinding.h" +#include "mozilla/dom/UnionConversions.h" +#include "mozilla/dom/URLBinding.h" +#include "mozilla/dom/URLSearchParamsBinding.h" +#include "mozilla/dom/XMLHttpRequest.h" +#include "mozilla/DeferredFinalize.h" + +using namespace mozilla; +using namespace JS; +using namespace xpc; + +using mozilla::dom::DestroyProtoAndIfaceCache; +using mozilla::dom::IndexedDatabaseManager; + +NS_IMPL_CYCLE_COLLECTION_CLASS(SandboxPrivate) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SandboxPrivate) + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER + tmp->UnlinkHostObjectURIs(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(SandboxPrivate) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS + tmp->TraverseHostObjectURIs(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(SandboxPrivate) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(SandboxPrivate) +NS_IMPL_CYCLE_COLLECTING_RELEASE(SandboxPrivate) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SandboxPrivate) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + +const char kScriptSecurityManagerContractID[] = NS_SCRIPTSECURITYMANAGER_CONTRACTID; + +class nsXPCComponents_utils_Sandbox : public nsIXPCComponents_utils_Sandbox, + public nsIXPCScriptable +{ +public: + // Aren't macros nice? + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_UTILS_SANDBOX + NS_DECL_NSIXPCSCRIPTABLE + +public: + nsXPCComponents_utils_Sandbox(); + +private: + virtual ~nsXPCComponents_utils_Sandbox(); + + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +already_AddRefed +xpc::NewSandboxConstructor() +{ + nsCOMPtr sbConstructor = + new nsXPCComponents_utils_Sandbox(); + return sbConstructor.forget(); +} + +static bool +SandboxDump(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) + return true; + + RootedString str(cx, ToString(cx, args[0])); + if (!str) + return false; + + JSAutoByteString utf8str; + char* cstr = utf8str.encodeUtf8(cx, str); + if (!cstr) + return false; + +#if defined(XP_MACOSX) + // Be nice and convert all \r to \n. + char* c = cstr; + char* cEnd = cstr + strlen(cstr); + while (c < cEnd) { + if (*c == '\r') + *c = '\n'; + c++; + } +#endif +#ifdef ANDROID + __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr); +#endif + + fputs(cstr, stdout); + fflush(stdout); + args.rval().setBoolean(true); + return true; +} + +static bool +SandboxDebug(JSContext* cx, unsigned argc, Value* vp) +{ +#ifdef DEBUG + return SandboxDump(cx, argc, vp); +#else + return true; +#endif +} + +static bool +SandboxImport(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() < 1 || args[0].isPrimitive()) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + + RootedString funname(cx); + if (args.length() > 1) { + // Use the second parameter as the function name. + funname = ToString(cx, args[1]); + if (!funname) + return false; + } else { + // NB: funobj must only be used to get the JSFunction out. + RootedObject funobj(cx, &args[0].toObject()); + if (js::IsProxy(funobj)) { + funobj = XPCWrapper::UnsafeUnwrapSecurityWrapper(funobj); + } + + JSAutoCompartment ac(cx, funobj); + + RootedValue funval(cx, ObjectValue(*funobj)); + JSFunction* fun = JS_ValueToFunction(cx, funval); + if (!fun) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + + // Use the actual function name as the name. + funname = JS_GetFunctionId(fun); + if (!funname) { + XPCThrower::Throw(NS_ERROR_INVALID_ARG, cx); + return false; + } + } + + RootedId id(cx); + if (!JS_StringToId(cx, funname, &id)) + return false; + + // We need to resolve the this object, because this function is used + // unbound and should still work and act on the original sandbox. + RootedObject thisObject(cx, JS_THIS_OBJECT(cx, vp)); + if (!thisObject) { + XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); + return false; + } + if (!JS_SetPropertyById(cx, thisObject, id, args[0])) + return false; + + args.rval().setUndefined(); + return true; +} + +static bool +SandboxCreateCrypto(JSContext* cx, JS::HandleObject obj) +{ + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + nsIGlobalObject* native = xpc::NativeGlobal(obj); + MOZ_ASSERT(native); + + dom::Crypto* crypto = new dom::Crypto(); + crypto->Init(native); + JS::RootedObject wrapped(cx, crypto->WrapObject(cx, nullptr)); + return JS_DefineProperty(cx, obj, "crypto", wrapped, JSPROP_ENUMERATE); +} + +#ifdef MOZ_WEBRTC +static bool +SandboxCreateRTCIdentityProvider(JSContext* cx, JS::HandleObject obj) +{ + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + nsCOMPtr nativeGlobal = xpc::NativeGlobal(obj); + MOZ_ASSERT(nativeGlobal); + + dom::RTCIdentityProviderRegistrar* registrar = + new dom::RTCIdentityProviderRegistrar(nativeGlobal); + JS::RootedObject wrapped(cx, registrar->WrapObject(cx, nullptr)); + return JS_DefineProperty(cx, obj, "rtcIdentityProvider", wrapped, JSPROP_ENUMERATE); +} +#endif + +static bool +SetFetchRequestFromValue(JSContext *cx, RequestOrUSVString& request, + const MutableHandleValue& requestOrUrl) +{ + RequestOrUSVStringArgument requestHolder(request); + bool noMatch = true; + if (requestOrUrl.isObject() && + !requestHolder.TrySetToRequest(cx, requestOrUrl, noMatch, false)) { + return false; + } + if (noMatch && + !requestHolder.TrySetToUSVString(cx, requestOrUrl, noMatch)) { + return false; + } + if (noMatch) { + return false; + } + return true; +} + +static bool +SandboxFetch(JSContext* cx, JS::HandleObject scope, const CallArgs& args) +{ + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "fetch requires at least 1 argument"); + return false; + } + + RequestOrUSVString request; + if (!SetFetchRequestFromValue(cx, request, args[0])) { + JS_ReportErrorASCII(cx, "fetch requires a string or Request in argument 1"); + return false; + } + RootedDictionary options(cx); + if (!options.Init(cx, args.hasDefined(1) ? args[1] : JS::NullHandleValue, + "Argument 2 of fetch", false)) { + return false; + } + nsCOMPtr global = xpc::NativeGlobal(scope); + if (!global) { + return false; + } + ErrorResult rv; + RefPtr response = + FetchRequest(global, Constify(request), Constify(options), rv); + if (rv.MaybeSetPendingException(cx)) { + return false; + } + + args.rval().setObject(*response->PromiseObj()); + return true; +} + +static bool SandboxFetchPromise(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); + if (SandboxFetch(cx, scope, args)) { + return true; + } + return ConvertExceptionToPromise(cx, scope, args.rval()); +} + + +static bool +SandboxCreateFetch(JSContext* cx, HandleObject obj) +{ + MOZ_ASSERT(JS_IsGlobalObject(obj)); + + return JS_DefineFunction(cx, obj, "fetch", SandboxFetchPromise, 2, 0) && + dom::RequestBinding::GetConstructorObject(cx) && + dom::ResponseBinding::GetConstructorObject(cx) && + dom::HeadersBinding::GetConstructorObject(cx); +} + +static bool +SandboxIsProxy(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "Function requires at least 1 argument"); + return false; + } + if (!args[0].isObject()) { + args.rval().setBoolean(false); + return true; + } + + RootedObject obj(cx, &args[0].toObject()); + obj = js::CheckedUnwrap(obj); + NS_ENSURE_TRUE(obj, false); + + args.rval().setBoolean(js::IsScriptedProxy(obj)); + return true; +} + +/* + * Expected type of the arguments and the return value: + * function exportFunction(function funToExport, + * object targetScope, + * [optional] object options) + */ +static bool +SandboxExportFunction(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 2) { + JS_ReportErrorASCII(cx, "Function requires at least 2 arguments"); + return false; + } + + RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); + return ExportFunction(cx, args[0], args[1], options, args.rval()); +} + +static bool +SandboxCreateObjectIn(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportErrorASCII(cx, "Function requires at least 1 argument"); + return false; + } + + RootedObject optionsObj(cx); + bool calledWithOptions = args.length() > 1; + if (calledWithOptions) { + if (!args[1].isObject()) { + JS_ReportErrorASCII(cx, "Expected the 2nd argument (options) to be an object"); + return false; + } + optionsObj = &args[1].toObject(); + } + + CreateObjectInOptions options(cx, optionsObj); + if (calledWithOptions && !options.Parse()) + return false; + + return xpc::CreateObjectIn(cx, args[0], options, args.rval()); +} + +static bool +SandboxCloneInto(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 2) { + JS_ReportErrorASCII(cx, "Function requires at least 2 arguments"); + return false; + } + + RootedValue options(cx, args.length() > 2 ? args[2] : UndefinedValue()); + return xpc::CloneInto(cx, args[0], args[1], options, args.rval()); +} + +static void +sandbox_finalize(js::FreeOp* fop, JSObject* obj) +{ + nsIScriptObjectPrincipal* sop = + static_cast(xpc_GetJSPrivate(obj)); + if (!sop) { + // sop can be null if CreateSandboxObject fails in the middle. + return; + } + + static_cast(sop)->ForgetGlobalObject(); + DestroyProtoAndIfaceCache(obj); + DeferredFinalize(sop); +} + +static void +sandbox_moved(JSObject* obj, const JSObject* old) +{ + // Note that this hook can be called before the private pointer is set. In + // this case the SandboxPrivate will not exist yet, so there is nothing to + // do. + nsIScriptObjectPrincipal* sop = + static_cast(xpc_GetJSPrivate(obj)); + if (sop) + static_cast(sop)->ObjectMoved(obj, old); +} + +static bool +writeToProto_setProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp, JS::ObjectOpResult& result) +{ + RootedObject proto(cx); + if (!JS_GetPrototype(cx, obj, &proto)) + return false; + + RootedValue receiver(cx, ObjectValue(*proto)); + return JS_ForwardSetPropertyTo(cx, proto, id, vp, receiver, result); +} + +static bool +writeToProto_getProperty(JSContext* cx, JS::HandleObject obj, JS::HandleId id, + JS::MutableHandleValue vp) +{ + RootedObject proto(cx); + if (!JS_GetPrototype(cx, obj, &proto)) + return false; + + return JS_GetPropertyById(cx, proto, id, vp); +} + +struct AutoSkipPropertyMirroring +{ + explicit AutoSkipPropertyMirroring(CompartmentPrivate* priv) : priv(priv) { + MOZ_ASSERT(!priv->skipWriteToGlobalPrototype); + priv->skipWriteToGlobalPrototype = true; + } + ~AutoSkipPropertyMirroring() { + MOZ_ASSERT(priv->skipWriteToGlobalPrototype); + priv->skipWriteToGlobalPrototype = false; + } + + private: + CompartmentPrivate* priv; +}; + +// This hook handles the case when writeToGlobalPrototype is set on the +// sandbox. This flag asks that any properties defined on the sandbox global +// also be defined on the sandbox global's prototype. Whenever one of these +// properties is changed (on either side), the change should be reflected on +// both sides. We use this functionality to create sandboxes that are +// essentially "sub-globals" of another global. This is useful for running +// add-ons in a separate compartment while still giving them access to the +// chrome window. +static bool +sandbox_addProperty(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) +{ + CompartmentPrivate* priv = CompartmentPrivate::Get(obj); + MOZ_ASSERT(priv->writeToGlobalPrototype); + + // Whenever JS_EnumerateStandardClasses is called, it defines the + // "undefined" property, even if it's already defined. We don't want to do + // anything in that case. + if (id == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_UNDEFINED)) + return true; + + // Avoid recursively triggering sandbox_addProperty in the + // JS_DefinePropertyById call below. + if (priv->skipWriteToGlobalPrototype) + return true; + + AutoSkipPropertyMirroring askip(priv); + + RootedObject proto(cx); + if (!JS_GetPrototype(cx, obj, &proto)) + return false; + + // After bug 1015790 is fixed, we should be able to remove this unwrapping. + RootedObject unwrappedProto(cx, js::UncheckedUnwrap(proto, /* stopAtWindowProxy = */ false)); + + Rooted pd(cx); + if (!JS_GetPropertyDescriptorById(cx, proto, id, &pd)) + return false; + + // This is a little icky. If the property exists and is not configurable, + // then JS_CopyPropertyFrom will throw an exception when we try to do a + // normal assignment since it will think we're trying to remove the + // non-configurability. So we do JS_SetPropertyById in that case. + // + // However, in the case of |const x = 3|, we get called once for + // JSOP_DEFCONST and once for JSOP_SETCONST. The first one creates the + // property as readonly and configurable. The second one changes the + // attributes to readonly and not configurable. If we use JS_SetPropertyById + // for the second call, it will throw an exception because the property is + // readonly. We have to use JS_CopyPropertyFrom since it ignores the + // readonly attribute (as it calls JSObject::defineProperty). See bug + // 1019181. + if (pd.object() && !pd.configurable()) { + if (!JS_SetPropertyById(cx, proto, id, v)) + return false; + } else { + if (!JS_CopyPropertyFrom(cx, id, unwrappedProto, obj, + MakeNonConfigurableIntoConfigurable)) + return false; + } + + if (!JS_GetPropertyDescriptorById(cx, obj, id, &pd)) + return false; + unsigned attrs = pd.attributes() & ~(JSPROP_GETTER | JSPROP_SETTER); + if (!JS_DefinePropertyById(cx, obj, id, v, + attrs | JSPROP_PROPOP_ACCESSORS | JSPROP_REDEFINE_NONCONFIGURABLE, + JS_PROPERTYOP_GETTER(writeToProto_getProperty), + JS_PROPERTYOP_SETTER(writeToProto_setProperty))) + return false; + + return true; +} + +#define XPCONNECT_SANDBOX_CLASS_METADATA_SLOT (XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET) + +static const js::ClassOps SandboxClassOps = { + nullptr, nullptr, nullptr, nullptr, + JS_EnumerateStandardClasses, JS_ResolveStandardClass, + JS_MayResolveStandardClass, + sandbox_finalize, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook, +}; + +static const js::ClassExtension SandboxClassExtension = { + nullptr, /* weakmapKeyDelegateOp */ + sandbox_moved /* objectMovedOp */ +}; + +static const js::Class SandboxClass = { + "Sandbox", + XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | + JSCLASS_FOREGROUND_FINALIZE, + &SandboxClassOps, + JS_NULL_CLASS_SPEC, + &SandboxClassExtension, + JS_NULL_OBJECT_OPS +}; + +// Note to whomever comes here to remove addProperty hooks: billm has promised +// to do the work for this class. +static const js::ClassOps SandboxWriteToProtoClassOps = { + sandbox_addProperty, nullptr, nullptr, nullptr, + JS_EnumerateStandardClasses, JS_ResolveStandardClass, + JS_MayResolveStandardClass, + sandbox_finalize, + nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook, +}; + +static const js::Class SandboxWriteToProtoClass = { + "Sandbox", + XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(1) | + JSCLASS_FOREGROUND_FINALIZE, + &SandboxWriteToProtoClassOps, + JS_NULL_CLASS_SPEC, + &SandboxClassExtension, + JS_NULL_OBJECT_OPS +}; + +static const JSFunctionSpec SandboxFunctions[] = { + JS_FS("dump", SandboxDump, 1,0), + JS_FS("debug", SandboxDebug, 1,0), + JS_FS("importFunction", SandboxImport, 1,0), + JS_FS_END +}; + +bool +xpc::IsSandbox(JSObject* obj) +{ + const js::Class* clasp = js::GetObjectClass(obj); + return clasp == &SandboxClass || clasp == &SandboxWriteToProtoClass; +} + +/***************************************************************************/ +nsXPCComponents_utils_Sandbox::nsXPCComponents_utils_Sandbox() +{ +} + +nsXPCComponents_utils_Sandbox::~nsXPCComponents_utils_Sandbox() +{ +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_utils_Sandbox) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_utils_Sandbox) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_utils_Sandbox) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_utils_Sandbox) +NS_IMPL_RELEASE(nsXPCComponents_utils_Sandbox) + +// We use the nsIXPScriptable macros to generate lots of stuff for us. +#define XPC_MAP_CLASSNAME nsXPCComponents_utils_Sandbox +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_utils_Sandbox" +#define XPC_MAP_WANT_CALL +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_FLAGS 0 +#include "xpc_map_end.h" /* This #undef's the above. */ + +const xpc::SandboxProxyHandler xpc::sandboxProxyHandler; + +bool +xpc::IsSandboxPrototypeProxy(JSObject* obj) +{ + return js::IsProxy(obj) && + js::GetProxyHandler(obj) == &xpc::sandboxProxyHandler; +} + +bool +xpc::SandboxCallableProxyHandler::call(JSContext* cx, JS::Handle proxy, + const JS::CallArgs& args) const +{ + // We forward the call to our underlying callable. + + // Get our SandboxProxyHandler proxy. + RootedObject sandboxProxy(cx, getSandboxProxy(proxy)); + MOZ_ASSERT(js::IsProxy(sandboxProxy) && + js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler); + + // The global of the sandboxProxy is the sandbox global, and the + // target object is the original proto. + RootedObject sandboxGlobal(cx, + js::GetGlobalForObjectCrossCompartment(sandboxProxy)); + MOZ_ASSERT(IsSandbox(sandboxGlobal)); + + // If our this object is the sandbox global, we call with this set to the + // original proto instead. + // + // There are two different ways we can compute |this|. If we use + // JS_THIS_VALUE, we'll get the bonafide |this| value as passed by the + // caller, which may be undefined if a global function was invoked without + // an explicit invocant. If we use JS_THIS or JS_THIS_OBJECT, the |this| + // in |vp| will be coerced to the global, which is not the correct + // behavior in ES5 strict mode. And we have no way to compute strictness + // here. + // + // The naive approach is simply to use JS_THIS_VALUE here. If |this| was + // explicit, we can remap it appropriately. If it was implicit, then we + // leave it as undefined, and let the callee sort it out. Since the callee + // is generally in the same compartment as its global (eg the Window's + // compartment, not the Sandbox's), the callee will generally compute the + // correct |this|. + // + // However, this breaks down in the Xray case. If the sandboxPrototype + // is an Xray wrapper, then we'll end up reifying the native methods in + // the Sandbox's scope, which means that they'll compute |this| to be the + // Sandbox, breaking old-style XPC_WN_CallMethod methods. + // + // Luckily, the intent of Xrays is to provide a vanilla view of a foreign + // DOM interface, which means that we don't care about script-enacted + // strictness in the prototype's home compartment. Indeed, since DOM + // methods are always non-strict, we can just assume non-strict semantics + // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately + // remap |this|. + bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy); + RootedValue thisVal(cx, isXray ? args.computeThis(cx) : args.thisv()); + if (thisVal == ObjectValue(*sandboxGlobal)) { + thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy)); + } + + RootedValue func(cx, js::GetProxyPrivate(proxy)); + return JS::Call(cx, thisVal, func, args, args.rval()); +} + +const xpc::SandboxCallableProxyHandler xpc::sandboxCallableProxyHandler; + +/* + * Wrap a callable such that if we're called with oldThisObj as the + * "this" we will instead call it with newThisObj as the this. + */ +static JSObject* +WrapCallable(JSContext* cx, HandleObject callable, HandleObject sandboxProtoProxy) +{ + MOZ_ASSERT(JS::IsCallable(callable)); + // Our proxy is wrapping the callable. So we need to use the + // callable as the private. We put the given sandboxProtoProxy in + // an extra slot, and our call() hook depends on that. + MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) && + js::GetProxyHandler(sandboxProtoProxy) == + &xpc::sandboxProxyHandler); + + RootedValue priv(cx, ObjectValue(*callable)); + // We want to claim to have the same proto as our wrapped callable, so set + // ourselves up with a lazy proto. + js::ProxyOptions options; + options.setLazyProto(true); + JSObject* obj = js::NewProxyObject(cx, &xpc::sandboxCallableProxyHandler, + priv, nullptr, options); + if (obj) { + js::SetProxyExtra(obj, SandboxCallableProxyHandler::SandboxProxySlot, + ObjectValue(*sandboxProtoProxy)); + } + + return obj; +} + +template +bool WrapAccessorFunction(JSContext* cx, Op& op, PropertyDescriptor* desc, + unsigned attrFlag, HandleObject sandboxProtoProxy) +{ + if (!op) { + return true; + } + + if (!(desc->attrs & attrFlag)) { + XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx); + return false; + } + + RootedObject func(cx, JS_FUNC_TO_DATA_PTR(JSObject*, op)); + func = WrapCallable(cx, func, sandboxProtoProxy); + if (!func) + return false; + op = JS_DATA_TO_FUNC_PTR(Op, func.get()); + return true; +} + +bool +xpc::SandboxProxyHandler::getPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) const +{ + JS::RootedObject obj(cx, wrappedObject(proxy)); + + MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy)); + if (!JS_GetPropertyDescriptorById(cx, obj, id, desc)) + return false; + + if (!desc.object()) + return true; // No property, nothing to do + + // Now fix up the getter/setter/value as needed to be bound to desc->obj. + if (!WrapAccessorFunction(cx, desc.getter(), desc.address(), + JSPROP_GETTER, proxy)) + return false; + if (!WrapAccessorFunction(cx, desc.setter(), desc.address(), + JSPROP_SETTER, proxy)) + return false; + if (desc.value().isObject()) { + RootedObject val (cx, &desc.value().toObject()); + if (JS::IsCallable(val)) { + val = WrapCallable(cx, val, proxy); + if (!val) + return false; + desc.value().setObject(*val); + } + } + + return true; +} + +bool +xpc::SandboxProxyHandler::getOwnPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) + const +{ + if (!getPropertyDescriptor(cx, proxy, id, desc)) + return false; + + if (desc.object() != wrappedObject(proxy)) + desc.object().set(nullptr); + + return true; +} + +/* + * Reuse the BaseProxyHandler versions of the derived traps that are implemented + * in terms of the fundamental traps. + */ + +bool +xpc::SandboxProxyHandler::has(JSContext* cx, JS::Handle proxy, + JS::Handle id, bool* bp) const +{ + // This uses getPropertyDescriptor for backward compatibility with + // the old BaseProxyHandler::has implementation. + Rooted desc(cx); + if (!getPropertyDescriptor(cx, proxy, id, &desc)) + return false; + + *bp = !!desc.object(); + return true; +} +bool +xpc::SandboxProxyHandler::hasOwn(JSContext* cx, JS::Handle proxy, + JS::Handle id, bool* bp) const +{ + return BaseProxyHandler::hasOwn(cx, proxy, id, bp); +} + +bool +xpc::SandboxProxyHandler::get(JSContext* cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + JS::MutableHandle vp) const +{ + // This uses getPropertyDescriptor for backward compatibility with + // the old BaseProxyHandler::get implementation. + Rooted desc(cx); + if (!getPropertyDescriptor(cx, proxy, id, &desc)) + return false; + desc.assertCompleteIfFound(); + + if (!desc.object()) { + vp.setUndefined(); + return true; + } + + // Everything after here follows [[Get]] for ordinary objects. + if (desc.isDataDescriptor()) { + vp.set(desc.value()); + return true; + } + + MOZ_ASSERT(desc.isAccessorDescriptor()); + RootedObject getter(cx, desc.getterObject()); + + if (!getter) { + vp.setUndefined(); + return true; + } + + return Call(cx, receiver, getter, HandleValueArray::empty(), vp); +} + +bool +xpc::SandboxProxyHandler::set(JSContext* cx, JS::Handle proxy, + JS::Handle id, + JS::Handle v, + JS::Handle receiver, + JS::ObjectOpResult& result) const +{ + return BaseProxyHandler::set(cx, proxy, id, v, receiver, result); +} + +bool +xpc::SandboxProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx, + JS::Handle proxy, + AutoIdVector& props) const +{ + return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); +} + +bool +xpc::SandboxProxyHandler::enumerate(JSContext* cx, JS::Handle proxy, + JS::MutableHandle objp) const +{ + return BaseProxyHandler::enumerate(cx, proxy, objp); +} + +bool +xpc::GlobalProperties::Parse(JSContext* cx, JS::HandleObject obj) +{ + uint32_t length; + bool ok = JS_GetArrayLength(cx, obj, &length); + NS_ENSURE_TRUE(ok, false); + for (uint32_t i = 0; i < length; i++) { + RootedValue nameValue(cx); + ok = JS_GetElement(cx, obj, i, &nameValue); + NS_ENSURE_TRUE(ok, false); + if (!nameValue.isString()) { + JS_ReportErrorASCII(cx, "Property names must be strings"); + return false; + } + RootedString nameStr(cx, nameValue.toString()); + JSAutoByteString name; + if (!name.encodeUtf8(cx, nameStr)) + return false; + if (!strcmp(name.ptr(), "CSS")) { + CSS = true; + } else if (!strcmp(name.ptr(), "indexedDB")) { + indexedDB = true; + } else if (!strcmp(name.ptr(), "XMLHttpRequest")) { + XMLHttpRequest = true; + } else if (!strcmp(name.ptr(), "TextEncoder")) { + TextEncoder = true; + } else if (!strcmp(name.ptr(), "TextDecoder")) { + TextDecoder = true; + } else if (!strcmp(name.ptr(), "URL")) { + URL = true; + } else if (!strcmp(name.ptr(), "URLSearchParams")) { + URLSearchParams = true; + } else if (!strcmp(name.ptr(), "atob")) { + atob = true; + } else if (!strcmp(name.ptr(), "btoa")) { + btoa = true; + } else if (!strcmp(name.ptr(), "Blob")) { + Blob = true; + } else if (!strcmp(name.ptr(), "Directory")) { + Directory = true; + } else if (!strcmp(name.ptr(), "File")) { + File = true; + } else if (!strcmp(name.ptr(), "crypto")) { + crypto = true; +#ifdef MOZ_WEBRTC + } else if (!strcmp(name.ptr(), "rtcIdentityProvider")) { + rtcIdentityProvider = true; +#endif + } else if (!strcmp(name.ptr(), "fetch")) { + fetch = true; + } else if (!strcmp(name.ptr(), "caches")) { + caches = true; + } else if (!strcmp(name.ptr(), "FileReader")) { + fileReader = true; + } else { + JS_ReportErrorUTF8(cx, "Unknown property name: %s", name.ptr()); + return false; + } + } + return true; +} + +bool +xpc::GlobalProperties::Define(JSContext* cx, JS::HandleObject obj) +{ + MOZ_ASSERT(js::GetContextCompartment(cx) == js::GetObjectCompartment(obj)); + // Properties will be exposed to System automatically but not to Sandboxes + // if |[Exposed=System]| is specified. + // This function holds common properties not exposed automatically but able + // to be requested either in |Cu.importGlobalProperties| or + // |wantGlobalProperties| of a sandbox. + if (CSS && !dom::CSSBinding::GetConstructorObject(cx)) + return false; + + if (XMLHttpRequest && + !dom::XMLHttpRequestBinding::GetConstructorObject(cx)) + return false; + + if (TextEncoder && + !dom::TextEncoderBinding::GetConstructorObject(cx)) + return false; + + if (TextDecoder && + !dom::TextDecoderBinding::GetConstructorObject(cx)) + return false; + + if (URL && + !dom::URLBinding::GetConstructorObject(cx)) + return false; + + if (URLSearchParams && + !dom::URLSearchParamsBinding::GetConstructorObject(cx)) + return false; + + if (atob && + !JS_DefineFunction(cx, obj, "atob", Atob, 1, 0)) + return false; + + if (btoa && + !JS_DefineFunction(cx, obj, "btoa", Btoa, 1, 0)) + return false; + + if (Blob && + !dom::BlobBinding::GetConstructorObject(cx)) + return false; + + if (Directory && + !dom::DirectoryBinding::GetConstructorObject(cx)) + return false; + + if (File && + !dom::FileBinding::GetConstructorObject(cx)) + return false; + + if (crypto && !SandboxCreateCrypto(cx, obj)) + return false; + +#ifdef MOZ_WEBRTC + if (rtcIdentityProvider && !SandboxCreateRTCIdentityProvider(cx, obj)) + return false; +#endif + + if (fetch && !SandboxCreateFetch(cx, obj)) + return false; + + if (caches && !dom::cache::CacheStorage::DefineCaches(cx, obj)) + return false; + + if (fileReader && !dom::FileReaderBinding::GetConstructorObject(cx)) + return false; + + return true; +} + +bool +xpc::GlobalProperties::DefineInXPCComponents(JSContext* cx, JS::HandleObject obj) +{ + if (indexedDB && + !IndexedDatabaseManager::DefineIndexedDB(cx, obj)) + return false; + + return Define(cx, obj); +} + +bool +xpc::GlobalProperties::DefineInSandbox(JSContext* cx, JS::HandleObject obj) +{ + MOZ_ASSERT(IsSandbox(obj)); + MOZ_ASSERT(js::GetContextCompartment(cx) == js::GetObjectCompartment(obj)); + + if (indexedDB && + !(IndexedDatabaseManager::ResolveSandboxBinding(cx) && + IndexedDatabaseManager::DefineIndexedDB(cx, obj))) + return false; + + return Define(cx, obj); +} + +nsresult +xpc::CreateSandboxObject(JSContext* cx, MutableHandleValue vp, nsISupports* prinOrSop, + SandboxOptions& options) +{ + // Create the sandbox global object + nsCOMPtr principal = do_QueryInterface(prinOrSop); + if (!principal) { + nsCOMPtr sop = do_QueryInterface(prinOrSop); + if (sop) { + principal = sop->GetPrincipal(); + } else { + RefPtr nullPrin = nsNullPrincipal::Create(); + principal = nullPrin; + } + } + MOZ_ASSERT(principal); + + JS::CompartmentOptions compartmentOptions; + + auto& creationOptions = compartmentOptions.creationOptions(); + + // XXXjwatt: Consider whether/when sandboxes should be able to see + // [SecureContext] API (bug 1273687). In that case we'd call + // creationOptions.setSecureContext(true). + + if (xpc::SharedMemoryEnabled()) + creationOptions.setSharedMemoryAndAtomicsEnabled(true); + + if (options.sameZoneAs) + creationOptions.setSameZoneAs(js::UncheckedUnwrap(options.sameZoneAs)); + else if (options.freshZone) + creationOptions.setZone(JS::FreshZone); + else + creationOptions.setZone(JS::SystemZone); + + creationOptions.setInvisibleToDebugger(options.invisibleToDebugger) + .setTrace(TraceXPCGlobal); + + // Try to figure out any addon this sandbox should be associated with. + // The addon could have been passed in directly, as part of the metadata, + // or by being constructed from an addon's code. + JSAddonId* addonId = nullptr; + if (options.addonId) { + addonId = JS::NewAddonId(cx, options.addonId); + NS_ENSURE_TRUE(addonId, NS_ERROR_FAILURE); + } else if (JSObject* obj = JS::CurrentGlobalOrNull(cx)) { + if (JSAddonId* id = JS::AddonIdOfObject(obj)) + addonId = id; + } + + creationOptions.setAddonId(addonId); + + compartmentOptions.behaviors().setDiscardSource(options.discardSource); + + const js::Class* clasp = options.writeToGlobalPrototype + ? &SandboxWriteToProtoClass + : &SandboxClass; + + RootedObject sandbox(cx, xpc::CreateGlobalObject(cx, js::Jsvalify(clasp), + principal, compartmentOptions)); + if (!sandbox) + return NS_ERROR_FAILURE; + + CompartmentPrivate* priv = CompartmentPrivate::Get(sandbox); + priv->allowWaivers = options.allowWaivers; + priv->writeToGlobalPrototype = options.writeToGlobalPrototype; + priv->isWebExtensionContentScript = options.isWebExtensionContentScript; + priv->waiveInterposition = options.waiveInterposition; + + // Set up the wantXrays flag, which indicates whether xrays are desired even + // for same-origin access. + // + // This flag has historically been ignored for chrome sandboxes due to + // quirks in the wrapping implementation that have now been removed. Indeed, + // same-origin Xrays for chrome->chrome access seems a bit superfluous. + // Arguably we should just flip the default for chrome and still honor the + // flag, but such a change would break code in subtle ways for minimal + // benefit. So we just switch it off here. + priv->wantXrays = + AccessCheck::isChrome(sandbox) ? false : options.wantXrays; + + { + JSAutoCompartment ac(cx, sandbox); + + nsCOMPtr sbp = + new SandboxPrivate(principal, sandbox); + + // Pass on ownership of sbp to |sandbox|. + JS_SetPrivate(sandbox, sbp.forget().take()); + + { + // Don't try to mirror standard class properties, if we're using a + // mirroring sandbox. (This is meaningless for non-mirroring + // sandboxes.) + AutoSkipPropertyMirroring askip(CompartmentPrivate::Get(sandbox)); + + // Ensure |Object.prototype| is instantiated before prototype- + // splicing below. For write-to-global-prototype behavior, extend + // this to all builtin properties. + if (options.writeToGlobalPrototype) { + if (!JS_EnumerateStandardClasses(cx, sandbox)) + return NS_ERROR_XPC_UNEXPECTED; + } else { + if (!JS_GetObjectPrototype(cx, sandbox)) + return NS_ERROR_XPC_UNEXPECTED; + } + } + + if (options.proto) { + bool ok = JS_WrapObject(cx, &options.proto); + if (!ok) + return NS_ERROR_XPC_UNEXPECTED; + + // Now check what sort of thing we've got in |proto|, and figure out + // if we need a SandboxProxyHandler. + // + // Note that, in the case of a window, we can't require that the + // Sandbox subsumes the prototype, because we have to hold our + // reference to it via an outer window, and the window may navigate + // at any time. So we have to handle that case separately. + bool useSandboxProxy = !!WindowOrNull(js::UncheckedUnwrap(options.proto, false)); + if (!useSandboxProxy) { + JSObject* unwrappedProto = js::CheckedUnwrap(options.proto, false); + if (!unwrappedProto) { + JS_ReportErrorASCII(cx, "Sandbox must subsume sandboxPrototype"); + return NS_ERROR_INVALID_ARG; + } + const js::Class* unwrappedClass = js::GetObjectClass(unwrappedProto); + useSandboxProxy = IS_WN_CLASS(unwrappedClass) || + mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass)); + } + + if (useSandboxProxy) { + // Wrap it up in a proxy that will do the right thing in terms + // of this-binding for methods. + RootedValue priv(cx, ObjectValue(*options.proto)); + options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler, + priv, nullptr); + if (!options.proto) + return NS_ERROR_OUT_OF_MEMORY; + } + + ok = JS_SplicePrototype(cx, sandbox, options.proto); + if (!ok) + return NS_ERROR_XPC_UNEXPECTED; + } + + // Don't try to mirror the properties that are set below. + AutoSkipPropertyMirroring askip(CompartmentPrivate::Get(sandbox)); + + bool allowComponents = principal == nsXPConnect::SystemPrincipal() || + nsContentUtils::IsExpandedPrincipal(principal); + if (options.wantComponents && allowComponents && + !ObjectScope(sandbox)->AttachComponentsObject(cx)) + return NS_ERROR_XPC_UNEXPECTED; + + if (!XPCNativeWrapper::AttachNewConstructorObject(cx, sandbox)) + return NS_ERROR_XPC_UNEXPECTED; + + if (!JS_DefineFunctions(cx, sandbox, SandboxFunctions)) + return NS_ERROR_XPC_UNEXPECTED; + + if (options.wantExportHelpers && + (!JS_DefineFunction(cx, sandbox, "exportFunction", SandboxExportFunction, 3, 0) || + !JS_DefineFunction(cx, sandbox, "createObjectIn", SandboxCreateObjectIn, 2, 0) || + !JS_DefineFunction(cx, sandbox, "cloneInto", SandboxCloneInto, 3, 0) || + !JS_DefineFunction(cx, sandbox, "isProxy", SandboxIsProxy, 1, 0))) + return NS_ERROR_XPC_UNEXPECTED; + + if (!options.globalProperties.DefineInSandbox(cx, sandbox)) + return NS_ERROR_XPC_UNEXPECTED; + +#ifndef SPIDERMONKEY_PROMISE + // Promise is supposed to be part of ES, and therefore should appear on + // every global. + if (!dom::PromiseBinding::GetConstructorObject(cx)) + return NS_ERROR_XPC_UNEXPECTED; +#endif // SPIDERMONKEY_PROMISE + } + + // We handle the case where the context isn't in a compartment for the + // benefit of InitSingletonScopes. + vp.setObject(*sandbox); + if (js::GetContextCompartment(cx) && !JS_WrapValue(cx, vp)) + return NS_ERROR_UNEXPECTED; + + // Set the location information for the new global, so that tools like + // about:memory may use that information + xpc::SetLocationForGlobal(sandbox, options.sandboxName); + + xpc::SetSandboxMetadata(cx, sandbox, options.metadata); + + JS_FireOnNewGlobalObject(cx, sandbox); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_utils_Sandbox::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_utils_Sandbox::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +/* + * For sandbox constructor the first argument can be a URI string in which case + * we use the related Codebase Principal for the sandbox. + */ +bool +ParsePrincipal(JSContext* cx, HandleString codebase, const PrincipalOriginAttributes& aAttrs, + nsIPrincipal** principal) +{ + MOZ_ASSERT(principal); + MOZ_ASSERT(codebase); + nsCOMPtr uri; + nsAutoJSString codebaseStr; + NS_ENSURE_TRUE(codebaseStr.init(cx, codebase), false); + nsresult rv = NS_NewURI(getter_AddRefs(uri), codebaseStr); + if (NS_FAILED(rv)) { + JS_ReportErrorASCII(cx, "Creating URI from string failed"); + return false; + } + + // We could allow passing in the app-id and browser-element info to the + // sandbox constructor. But creating a sandbox based on a string is a + // deprecated API so no need to add features to it. + nsCOMPtr prin = + BasePrincipal::CreateCodebasePrincipal(uri, aAttrs); + prin.forget(principal); + + if (!*principal) { + JS_ReportErrorASCII(cx, "Creating Principal from URI failed"); + return false; + } + return true; +} + +/* + * For sandbox constructor the first argument can be a principal object or + * a script object principal (Document, Window). + */ +static bool +GetPrincipalOrSOP(JSContext* cx, HandleObject from, nsISupports** out) +{ + MOZ_ASSERT(out); + *out = nullptr; + + nsCOMPtr native = xpc::UnwrapReflectorToISupports(from); + + if (nsCOMPtr sop = do_QueryInterface(native)) { + sop.forget(out); + return true; + } + + nsCOMPtr principal = do_QueryInterface(native); + principal.forget(out); + NS_ENSURE_TRUE(*out, false); + + return true; +} + +/* + * The first parameter of the sandbox constructor might be an array of principals, either in string + * format or actual objects (see GetPrincipalOrSOP) + */ +static bool +GetExpandedPrincipal(JSContext* cx, HandleObject arrayObj, + const SandboxOptions& options, nsIExpandedPrincipal** out) +{ + MOZ_ASSERT(out); + uint32_t length; + + if (!JS_GetArrayLength(cx, arrayObj, &length)) + return false; + if (!length) { + // We need a whitelist of principals or uri strings to create an + // expanded principal, if we got an empty array or something else + // report error. + JS_ReportErrorASCII(cx, "Expected an array of URI strings"); + return false; + } + + nsTArray< nsCOMPtr > allowedDomains(length); + allowedDomains.SetLength(length); + + // If an originAttributes option has been specified, we will use that as the + // OriginAttribute of all of the string arguments passed to this function. + // Otherwise, we will use the OriginAttributes of a principal or SOP object + // in the array, if any. If no such object is present, and all we have are + // strings, then we will use a default OriginAttribute. + // Otherwise, we will use the origin attributes of the passed object(s). If + // more than one object is specified, we ensure that the OAs match. + Maybe attrs; + if (options.originAttributes) { + attrs.emplace(); + JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes)); + if (!attrs->Init(cx, val)) { + // The originAttributes option, if specified, must be valid! + JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object"); + return false; + } + } + + // Now we go over the array in two passes. In the first pass, we ignore + // strings, and only process objects. Assuming that no originAttributes + // option has been passed, if we encounter a principal or SOP object, we + // grab its OA and save it if it's the first OA encountered, otherwise + // check to make sure that it is the same as the OA found before. + // In the second pass, we ignore objects, and use the OA found in pass 0 + // (or the previously computed OA if we have obtained it from the options) + // to construct codebase principals. + // + // The effective OA selected above will also be set as the OA of the + // expanded principal object. + + // First pass: + for (uint32_t i = 0; i < length; ++i) { + RootedValue allowed(cx); + if (!JS_GetElement(cx, arrayObj, i, &allowed)) + return false; + + nsresult rv; + nsCOMPtr principal; + if (allowed.isObject()) { + // In case of object let's see if it's a Principal or a ScriptObjectPrincipal. + nsCOMPtr prinOrSop; + RootedObject obj(cx, &allowed.toObject()); + if (!GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop))) + return false; + + nsCOMPtr sop(do_QueryInterface(prinOrSop)); + principal = do_QueryInterface(prinOrSop); + if (sop) + principal = sop->GetPrincipal(); + NS_ENSURE_TRUE(principal, false); + + if (!options.originAttributes) { + const PrincipalOriginAttributes prinAttrs = + BasePrincipal::Cast(principal)->OriginAttributesRef(); + if (attrs.isNothing()) { + attrs.emplace(prinAttrs); + } else if (prinAttrs != attrs.ref()) { + // If attrs is from a previously encountered principal in the + // array, we need to ensure that it matches the OA of the + // principal we have here. + // If attrs comes from OriginAttributes, we don't need + // this check. + return false; + } + } + + // We do not allow ExpandedPrincipals to contain any system principals. + bool isSystem; + rv = nsXPConnect::SecurityManager()->IsSystemPrincipal(principal, &isSystem); + NS_ENSURE_SUCCESS(rv, false); + if (isSystem) { + JS_ReportErrorASCII(cx, "System principal is not allowed in an expanded principal"); + return false; + } + allowedDomains[i] = principal; + } else if (allowed.isString()) { + // Skip any string arguments - we handle them in the next pass. + } else { + // Don't know what this is. + return false; + } + } + + if (attrs.isNothing()) { + // If no OriginAttributes was found in the first pass, fall back to a default one. + attrs.emplace(); + } + + // Second pass: + for (uint32_t i = 0; i < length; ++i) { + RootedValue allowed(cx); + if (!JS_GetElement(cx, arrayObj, i, &allowed)) + return false; + + nsCOMPtr principal; + if (allowed.isString()) { + // In case of string let's try to fetch a codebase principal from it. + RootedString str(cx, allowed.toString()); + + // attrs here is either a default OriginAttributes in case the + // originAttributes option isn't specified, and no object in the array + // provides a principal. Otherwise it's either the forced principal, or + // the principal found before, so we can use it here. + if (!ParsePrincipal(cx, str, attrs.ref(), getter_AddRefs(principal))) + return false; + NS_ENSURE_TRUE(principal, false); + allowedDomains[i] = principal; + } else { + MOZ_ASSERT(allowed.isObject()); + } + } + + nsCOMPtr result = + new nsExpandedPrincipal(allowedDomains, attrs.ref()); + result.forget(out); + return true; +} + +/* + * Helper that tries to get a property from the options object. + */ +bool +OptionsBase::ParseValue(const char* name, MutableHandleValue prop, bool* aFound) +{ + bool found; + bool ok = JS_HasProperty(mCx, mObject, name, &found); + NS_ENSURE_TRUE(ok, false); + + if (aFound) + *aFound = found; + + if (!found) + return true; + + return JS_GetProperty(mCx, mObject, name, prop); +} + +/* + * Helper that tries to get a boolean property from the options object. + */ +bool +OptionsBase::ParseBoolean(const char* name, bool* prop) +{ + MOZ_ASSERT(prop); + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + if (!value.isBoolean()) { + JS_ReportErrorASCII(mCx, "Expected a boolean value for property %s", name); + return false; + } + + *prop = value.toBoolean(); + return true; +} + +/* + * Helper that tries to get an object property from the options object. + */ +bool +OptionsBase::ParseObject(const char* name, MutableHandleObject prop) +{ + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + if (!value.isObject()) { + JS_ReportErrorASCII(mCx, "Expected an object value for property %s", name); + return false; + } + prop.set(&value.toObject()); + return true; +} + +/* + * Helper that tries to get an object property from the options object. + */ +bool +OptionsBase::ParseJSString(const char* name, MutableHandleString prop) +{ + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + if (!value.isString()) { + JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); + return false; + } + prop.set(value.toString()); + return true; +} + +/* + * Helper that tries to get a string property from the options object. + */ +bool +OptionsBase::ParseString(const char* name, nsCString& prop) +{ + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + if (!value.isString()) { + JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); + return false; + } + + char* tmp = JS_EncodeString(mCx, value.toString()); + NS_ENSURE_TRUE(tmp, false); + prop.Assign(tmp, strlen(tmp)); + js_free(tmp); + return true; +} + +/* + * Helper that tries to get a string property from the options object. + */ +bool +OptionsBase::ParseString(const char* name, nsString& prop) +{ + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + if (!value.isString()) { + JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name); + return false; + } + + nsAutoJSString strVal; + if (!strVal.init(mCx, value.toString())) + return false; + + prop = strVal; + return true; +} + +/* + * Helper that tries to get jsid property from the options object. + */ +bool +OptionsBase::ParseId(const char* name, MutableHandleId prop) +{ + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + return JS_ValueToId(mCx, value, prop); +} + +/* + * Helper that tries to get a uint32_t property from the options object. + */ +bool +OptionsBase::ParseUInt32(const char* name, uint32_t* prop) +{ + MOZ_ASSERT(prop); + RootedValue value(mCx); + bool found; + bool ok = ParseValue(name, &value, &found); + NS_ENSURE_TRUE(ok, false); + + if (!found) + return true; + + if(!JS::ToUint32(mCx, value, prop)) { + JS_ReportErrorASCII(mCx, "Expected a uint32_t value for property %s", name); + return false; + } + + return true; +} + +/* + * Helper that tries to get a list of DOM constructors and other helpers from the options object. + */ +bool +SandboxOptions::ParseGlobalProperties() +{ + RootedValue value(mCx); + bool found; + bool ok = ParseValue("wantGlobalProperties", &value, &found); + NS_ENSURE_TRUE(ok, false); + if (!found) + return true; + + if (!value.isObject()) { + JS_ReportErrorASCII(mCx, "Expected an array value for wantGlobalProperties"); + return false; + } + + RootedObject ctors(mCx, &value.toObject()); + bool isArray; + if (!JS_IsArrayObject(mCx, ctors, &isArray)) + return false; + if (!isArray) { + JS_ReportErrorASCII(mCx, "Expected an array value for wantGlobalProperties"); + return false; + } + + return globalProperties.Parse(mCx, ctors); +} + +/* + * Helper that parsing the sandbox options object (from) and sets the fields of the incoming options struct (options). + */ +bool +SandboxOptions::Parse() +{ + /* All option names must be ASCII-only. */ + bool ok = ParseObject("sandboxPrototype", &proto) && + ParseBoolean("wantXrays", &wantXrays) && + ParseBoolean("allowWaivers", &allowWaivers) && + ParseBoolean("wantComponents", &wantComponents) && + ParseBoolean("wantExportHelpers", &wantExportHelpers) && + ParseBoolean("isWebExtensionContentScript", &isWebExtensionContentScript) && + ParseBoolean("waiveInterposition", &waiveInterposition) && + ParseString("sandboxName", sandboxName) && + ParseObject("sameZoneAs", &sameZoneAs) && + ParseBoolean("freshZone", &freshZone) && + ParseBoolean("invisibleToDebugger", &invisibleToDebugger) && + ParseBoolean("discardSource", &discardSource) && + ParseJSString("addonId", &addonId) && + ParseBoolean("writeToGlobalPrototype", &writeToGlobalPrototype) && + ParseGlobalProperties() && + ParseValue("metadata", &metadata) && + ParseUInt32("userContextId", &userContextId) && + ParseObject("originAttributes", &originAttributes); + if (!ok) + return false; + + if (freshZone && sameZoneAs) { + JS_ReportErrorASCII(mCx, "Cannot use both sameZoneAs and freshZone"); + return false; + } + + return true; +} + +static nsresult +AssembleSandboxMemoryReporterName(JSContext* cx, nsCString& sandboxName) +{ + // Use a default name when the caller did not provide a sandboxName. + if (sandboxName.IsEmpty()) + sandboxName = NS_LITERAL_CSTRING("[anonymous sandbox]"); + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + // Get the xpconnect native call context. + nsAXPCNativeCallContext* cc = nullptr; + xpc->GetCurrentNativeCallContext(&cc); + NS_ENSURE_TRUE(cc, NS_ERROR_INVALID_ARG); + + // Get the current source info from xpc. + nsCOMPtr frame; + xpc->GetCurrentJSStack(getter_AddRefs(frame)); + + // Append the caller's location information. + if (frame) { + nsString location; + int32_t lineNumber = 0; + frame->GetFilename(cx, location); + frame->GetLineNumber(cx, &lineNumber); + + sandboxName.AppendLiteral(" (from: "); + sandboxName.Append(NS_ConvertUTF16toUTF8(location)); + sandboxName.Append(':'); + sandboxName.AppendInt(lineNumber); + sandboxName.Append(')'); + } + + return NS_OK; +} + +// static +nsresult +nsXPCComponents_utils_Sandbox::CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) +{ + if (args.length() < 1) + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + + nsresult rv; + bool ok = false; + bool calledWithOptions = args.length() > 1; + if (calledWithOptions && !args[1].isObject()) + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + + RootedObject optionsObject(cx, calledWithOptions ? &args[1].toObject() + : nullptr); + + SandboxOptions options(cx, optionsObject); + if (calledWithOptions && !options.Parse()) + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + + // Make sure to set up principals on the sandbox before initing classes. + nsCOMPtr principal; + nsCOMPtr expanded; + nsCOMPtr prinOrSop; + + if (args[0].isString()) { + RootedString str(cx, args[0].toString()); + PrincipalOriginAttributes attrs; + if (options.originAttributes) { + JS::RootedValue val(cx, JS::ObjectValue(*options.originAttributes)); + if (!attrs.Init(cx, val)) { + // The originAttributes option, if specified, must be valid! + JS_ReportErrorASCII(cx, "Expected a valid OriginAttributes object"); + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + } + } + attrs.mUserContextId = options.userContextId; + ok = ParsePrincipal(cx, str, attrs, getter_AddRefs(principal)); + prinOrSop = principal; + } else if (args[0].isObject()) { + RootedObject obj(cx, &args[0].toObject()); + bool isArray; + if (!JS_IsArrayObject(cx, obj, &isArray)) { + ok = false; + } else if (isArray) { + if (options.userContextId != 0) { + // We don't support passing a userContextId with an array. + ok = false; + } else { + ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded)); + prinOrSop = expanded; + } + } else { + ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop)); + } + } else if (args[0].isNull()) { + // Null means that we just pass prinOrSop = nullptr, and get an + // nsNullPrincipal. + ok = true; + } + + if (!ok) + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + + + if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) + return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval); + + if (options.metadata.isNullOrUndefined()) { + // If the caller is running in a sandbox, inherit. + RootedObject callerGlobal(cx, CurrentGlobalOrNull(cx)); + if (IsSandbox(callerGlobal)) { + rv = GetSandboxMetadata(cx, callerGlobal, &options.metadata); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + } + } + + rv = CreateSandboxObject(cx, args.rval(), prinOrSop, options); + + if (NS_FAILED(rv)) + return ThrowAndFail(rv, cx, _retval); + + // We have this crazy behavior where wantXrays=false also implies that the + // returned sandbox is implicitly waived. We've stopped advertising it, but + // keep supporting it for now. + if (!options.wantXrays && !xpc::WrapperFactory::WaiveXrayAndWrap(cx, args.rval())) + return NS_ERROR_UNEXPECTED; + + *_retval = true; + return NS_OK; +} + +nsresult +xpc::EvalInSandbox(JSContext* cx, HandleObject sandboxArg, const nsAString& source, + const nsACString& filename, int32_t lineNo, + JSVersion jsVersion, MutableHandleValue rval) +{ + JS_AbortIfWrongThread(cx); + rval.set(UndefinedValue()); + + bool waiveXray = xpc::WrapperFactory::HasWaiveXrayFlag(sandboxArg); + RootedObject sandbox(cx, js::CheckedUnwrap(sandboxArg)); + if (!sandbox || !IsSandbox(sandbox)) { + return NS_ERROR_INVALID_ARG; + } + + nsIScriptObjectPrincipal* sop = + static_cast(xpc_GetJSPrivate(sandbox)); + MOZ_ASSERT(sop, "Invalid sandbox passed"); + SandboxPrivate* priv = static_cast(sop); + nsCOMPtr prin = sop->GetPrincipal(); + NS_ENSURE_TRUE(prin, NS_ERROR_FAILURE); + + nsAutoCString filenameBuf; + if (!filename.IsVoid() && filename.Length() != 0) { + filenameBuf.Assign(filename); + } else { + // Default to the spec of the principal. + nsresult rv = nsJSPrincipals::get(prin)->GetScriptLocation(filenameBuf); + NS_ENSURE_SUCCESS(rv, rv); + lineNo = 1; + } + + // We create a separate cx to do the sandbox evaluation. Scope it. + RootedValue v(cx, UndefinedValue()); + RootedValue exn(cx, UndefinedValue()); + bool ok = true; + { + // We're about to evaluate script, so make an AutoEntryScript. + // This is clearly Gecko-specific and not in any spec. + mozilla::dom::AutoEntryScript aes(priv, "XPConnect sandbox evaluation"); + JSContext* sandcx = aes.cx(); + JSAutoCompartment ac(sandcx, sandbox); + + JS::CompileOptions options(sandcx); + options.setFileAndLine(filenameBuf.get(), lineNo) + .setVersion(jsVersion); + MOZ_ASSERT(JS_IsGlobalObject(sandbox)); + ok = JS::Evaluate(sandcx, options, + PromiseFlatString(source).get(), source.Length(), &v); + + // If the sandbox threw an exception, grab it off the context. + if (aes.HasException()) { + if (!aes.StealException(&exn)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + } + + // + // Alright, we're back on the caller's cx. If an error occured, try to + // wrap and set the exception. Otherwise, wrap the return value. + // + + if (!ok) { + // If we end up without an exception, it was probably due to OOM along + // the way, in which case we thow. Otherwise, wrap it. + if (exn.isUndefined() || !JS_WrapValue(cx, &exn)) + return NS_ERROR_OUT_OF_MEMORY; + + // Set the exception on our caller's cx. + JS_SetPendingException(cx, exn); + return NS_ERROR_FAILURE; + } + + // Transitively apply Xray waivers if |sb| was waived. + if (waiveXray) { + ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v); + } else { + ok = JS_WrapValue(cx, &v); + } + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + + // Whew! + rval.set(v); + return NS_OK; +} + +nsresult +xpc::GetSandboxAddonId(JSContext* cx, HandleObject sandbox, MutableHandleValue rval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsSandbox(sandbox)); + + JSAddonId* id = JS::AddonIdOfObject(sandbox); + if (!id) { + rval.setNull(); + return NS_OK; + } + + JS::RootedValue idStr(cx, StringValue(JS::StringOfAddonId(id))); + if (!JS_WrapValue(cx, &idStr)) + return NS_ERROR_UNEXPECTED; + + rval.set(idStr); + return NS_OK; +} + +nsresult +xpc::GetSandboxMetadata(JSContext* cx, HandleObject sandbox, MutableHandleValue rval) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsSandbox(sandbox)); + + RootedValue metadata(cx); + { + JSAutoCompartment ac(cx, sandbox); + metadata = JS_GetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT); + } + + if (!JS_WrapValue(cx, &metadata)) + return NS_ERROR_UNEXPECTED; + + rval.set(metadata); + return NS_OK; +} + +nsresult +xpc::SetSandboxMetadata(JSContext* cx, HandleObject sandbox, HandleValue metadataArg) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsSandbox(sandbox)); + + RootedValue metadata(cx); + + JSAutoCompartment ac(cx, sandbox); + if (!JS_StructuredClone(cx, metadataArg, &metadata, nullptr, nullptr)) + return NS_ERROR_UNEXPECTED; + + JS_SetReservedSlot(sandbox, XPCONNECT_SANDBOX_CLASS_METADATA_SLOT, metadata); + + return NS_OK; +} diff --git a/js/xpconnect/src/SandboxPrivate.h b/js/xpconnect/src/SandboxPrivate.h new file mode 100644 index 000000000..9f84ec788 --- /dev/null +++ b/js/xpconnect/src/SandboxPrivate.h @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __SANDBOXPRIVATE_H__ +#define __SANDBOXPRIVATE_H__ + +#include "nsIGlobalObject.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIPrincipal.h" +#include "nsWeakReference.h" +#include "nsWrapperCache.h" + +#include "js/RootingAPI.h" + + +class SandboxPrivate : public nsIGlobalObject, + public nsIScriptObjectPrincipal, + public nsSupportsWeakReference, + public nsWrapperCache +{ +public: + SandboxPrivate(nsIPrincipal* principal, JSObject* global) + : mPrincipal(principal) + { + SetIsNotDOMBinding(); + SetWrapper(global); + } + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(SandboxPrivate, + nsIGlobalObject) + + nsIPrincipal* GetPrincipal() override + { + return mPrincipal; + } + + JSObject* GetGlobalJSObject() override + { + return GetWrapper(); + } + + void ForgetGlobalObject() + { + ClearWrapper(); + } + + virtual JSObject* WrapObject(JSContext* cx, JS::Handle aGivenProto) override + { + MOZ_CRASH("SandboxPrivate doesn't use DOM bindings!"); + } + + void ObjectMoved(JSObject* obj, const JSObject* old) + { + UpdateWrapper(obj, old); + } + +private: + virtual ~SandboxPrivate() { } + + nsCOMPtr mPrincipal; +}; + +#endif // __SANDBOXPRIVATE_H__ diff --git a/js/xpconnect/src/XPCCallContext.cpp b/js/xpconnect/src/XPCCallContext.cpp new file mode 100644 index 000000000..22081e3cf --- /dev/null +++ b/js/xpconnect/src/XPCCallContext.cpp @@ -0,0 +1,276 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Call context. */ + +#include "xpcprivate.h" +#include "jswrapper.h" +#include "jsfriendapi.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace xpc; +using namespace JS; + +#define IS_TEAROFF_CLASS(clazz) ((clazz) == &XPC_WN_Tearoff_JSClass) + +XPCCallContext::XPCCallContext(JSContext* cx, + HandleObject obj /* = nullptr */, + HandleObject funobj /* = nullptr */, + HandleId name /* = JSID_VOID */, + unsigned argc /* = NO_ARGS */, + Value* argv /* = nullptr */, + Value* rval /* = nullptr */) + : mAr(cx), + mState(INIT_FAILED), + mXPC(nsXPConnect::XPConnect()), + mXPCJSContext(nullptr), + mJSContext(cx), + mWrapper(nullptr), + mTearOff(nullptr), + mName(cx) +{ + MOZ_ASSERT(cx); + MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); + + if (!mXPC) + return; + + mXPCJSContext = XPCJSContext::Get(); + + // hook into call context chain. + mPrevCallContext = mXPCJSContext->SetCallContext(this); + + mState = HAVE_CONTEXT; + + if (!obj) + return; + + mMethodIndex = 0xDEAD; + + mState = HAVE_OBJECT; + + mTearOff = nullptr; + + JSObject* unwrapped = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + if (!unwrapped) { + JS_ReportErrorASCII(mJSContext, "Permission denied to call method on |this|"); + mState = INIT_FAILED; + return; + } + const js::Class* clasp = js::GetObjectClass(unwrapped); + if (IS_WN_CLASS(clasp)) { + mWrapper = XPCWrappedNative::Get(unwrapped); + } else if (IS_TEAROFF_CLASS(clasp)) { + mTearOff = (XPCWrappedNativeTearOff*)js::GetObjectPrivate(unwrapped); + mWrapper = XPCWrappedNative::Get( + &js::GetReservedSlot(unwrapped, + XPC_WN_TEAROFF_FLAT_OBJECT_SLOT).toObject()); + } + if (mWrapper) { + if (mTearOff) + mScriptableInfo = nullptr; + else + mScriptableInfo = mWrapper->GetScriptableInfo(); + } + + if (!JSID_IS_VOID(name)) + SetName(name); + + if (argc != NO_ARGS) + SetArgsAndResultPtr(argc, argv, rval); + + CHECK_STATE(HAVE_OBJECT); +} + +void +XPCCallContext::SetName(jsid name) +{ + CHECK_STATE(HAVE_OBJECT); + + mName = name; + + if (mTearOff) { + mSet = nullptr; + mInterface = mTearOff->GetInterface(); + mMember = mInterface->FindMember(mName); + mStaticMemberIsLocal = true; + if (mMember && !mMember->IsConstant()) + mMethodIndex = mMember->GetIndex(); + } else { + mSet = mWrapper ? mWrapper->GetSet() : nullptr; + + if (mSet && + mSet->FindMember(mName, &mMember, &mInterface, + mWrapper->HasProto() ? + mWrapper->GetProto()->GetSet() : + nullptr, + &mStaticMemberIsLocal)) { + if (mMember && !mMember->IsConstant()) + mMethodIndex = mMember->GetIndex(); + } else { + mMember = nullptr; + mInterface = nullptr; + mStaticMemberIsLocal = false; + } + } + + mState = HAVE_NAME; +} + +void +XPCCallContext::SetCallInfo(XPCNativeInterface* iface, XPCNativeMember* member, + bool isSetter) +{ + CHECK_STATE(HAVE_CONTEXT); + + // We are going straight to the method info and need not do a lookup + // by id. + + // don't be tricked if method is called with wrong 'this' + if (mTearOff && mTearOff->GetInterface() != iface) + mTearOff = nullptr; + + mSet = nullptr; + mInterface = iface; + mMember = member; + mMethodIndex = mMember->GetIndex() + (isSetter ? 1 : 0); + mName = mMember->GetName(); + + if (mState < HAVE_NAME) + mState = HAVE_NAME; +} + +void +XPCCallContext::SetArgsAndResultPtr(unsigned argc, + Value* argv, + Value* rval) +{ + CHECK_STATE(HAVE_OBJECT); + + if (mState < HAVE_NAME) { + mSet = nullptr; + mInterface = nullptr; + mMember = nullptr; + mStaticMemberIsLocal = false; + } + + mArgc = argc; + mArgv = argv; + mRetVal = rval; + + mState = HAVE_ARGS; +} + +nsresult +XPCCallContext::CanCallNow() +{ + nsresult rv; + + if (!HasInterfaceAndMember()) + return NS_ERROR_UNEXPECTED; + if (mState < HAVE_ARGS) + return NS_ERROR_UNEXPECTED; + + if (!mTearOff) { + mTearOff = mWrapper->FindTearOff(mInterface, false, &rv); + if (!mTearOff || mTearOff->GetInterface() != mInterface) { + mTearOff = nullptr; + return NS_FAILED(rv) ? rv : NS_ERROR_UNEXPECTED; + } + } + + // Refresh in case FindTearOff extended the set + mSet = mWrapper->GetSet(); + + mState = READY_TO_CALL; + return NS_OK; +} + +void +XPCCallContext::SystemIsBeingShutDown() +{ + // XXX This is pretty questionable since the per thread cleanup stuff + // can be making this call on one thread for call contexts on another + // thread. + NS_WARNING("Shutting Down XPConnect even through there is a live XPCCallContext"); + mXPCJSContext = nullptr; + mState = SYSTEM_SHUTDOWN; + mSet = nullptr; + mInterface = nullptr; + + if (mPrevCallContext) + mPrevCallContext->SystemIsBeingShutDown(); +} + +XPCCallContext::~XPCCallContext() +{ + if (mXPCJSContext) { + DebugOnly old = mXPCJSContext->SetCallContext(mPrevCallContext); + MOZ_ASSERT(old == this, "bad pop from per thread data"); + } +} + +NS_IMETHODIMP +XPCCallContext::GetCallee(nsISupports * *aCallee) +{ + nsCOMPtr rval = mWrapper ? mWrapper->GetIdentityObject() : nullptr; + rval.forget(aCallee); + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetCalleeMethodIndex(uint16_t* aCalleeMethodIndex) +{ + *aCalleeMethodIndex = mMethodIndex; + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetCalleeInterface(nsIInterfaceInfo * *aCalleeInterface) +{ + nsCOMPtr rval = mInterface->GetInterfaceInfo(); + rval.forget(aCalleeInterface); + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetCalleeClassInfo(nsIClassInfo * *aCalleeClassInfo) +{ + nsCOMPtr rval = mWrapper ? mWrapper->GetClassInfo() : nullptr; + rval.forget(aCalleeClassInfo); + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetJSContext(JSContext * *aJSContext) +{ + JS_AbortIfWrongThread(mJSContext); + *aJSContext = mJSContext; + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetArgc(uint32_t* aArgc) +{ + *aArgc = (uint32_t) mArgc; + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetArgvPtr(Value** aArgvPtr) +{ + *aArgvPtr = mArgv; + return NS_OK; +} + +NS_IMETHODIMP +XPCCallContext::GetPreviousCallContext(nsAXPCNativeCallContext** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = GetPrevCallContext(); + return NS_OK; +} diff --git a/js/xpconnect/src/XPCComponents.cpp b/js/xpconnect/src/XPCComponents.cpp new file mode 100644 index 000000000..ca78234c9 --- /dev/null +++ b/js/xpconnect/src/XPCComponents.cpp @@ -0,0 +1,3563 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* The "Components" xpcom objects for JavaScript. */ + +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "xpcIJSModuleLoader.h" +#include "XPCJSWeakReference.h" +#include "WrapperFactory.h" +#include "nsJSUtils.h" +#include "mozJSComponentLoader.h" +#include "nsContentUtils.h" +#include "nsCycleCollector.h" +#include "jsfriendapi.h" +#include "js/StructuredClone.h" +#include "mozilla/Attributes.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "mozilla/Preferences.h" +#include "nsJSEnvironment.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/XPTInterfaceInfoManager.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/StructuredCloneTags.h" +#include "mozilla/dom/WindowBinding.h" +#include "nsZipArchive.h" +#include "nsIDOMFileList.h" +#include "nsWindowMemoryReporter.h" +#include "nsDOMClassInfo.h" +#include "ShimInterfaceInfo.h" +#include "nsIAddonInterposition.h" +#include "nsISimpleEnumerator.h" +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" + +using namespace mozilla; +using namespace JS; +using namespace js; +using namespace xpc; +using mozilla::dom::Exception; + +/***************************************************************************/ +// stuff used by all + +nsresult +xpc::ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval) +{ + XPCThrower::Throw(errNum, cx); + *retval = false; + return NS_OK; +} + +static bool +JSValIsInterfaceOfType(JSContext* cx, HandleValue v, REFNSIID iid) +{ + + nsCOMPtr wn; + nsCOMPtr iface; + + if (v.isPrimitive()) + return false; + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + RootedObject obj(cx, &v.toObject()); + return NS_SUCCEEDED(xpc->GetWrappedNativeOfJSObject(cx, obj, getter_AddRefs(wn))) && wn && + NS_SUCCEEDED(wn->Native()->QueryInterface(iid, getter_AddRefs(iface))) && iface; +} + +char* +xpc::CloneAllAccess() +{ + static const char allAccess[] = "AllAccess"; + return (char*)nsMemory::Clone(allAccess, sizeof(allAccess)); +} + +char* +xpc::CheckAccessList(const char16_t* wideName, const char* const list[]) +{ + nsAutoCString asciiName; + CopyUTF16toUTF8(nsDependentString(wideName), asciiName); + + for (const char* const* p = list; *p; p++) + if (!strcmp(*p, asciiName.get())) + return CloneAllAccess(); + + return nullptr; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + + +class nsXPCComponents_Interfaces final : + public nsIXPCComponents_Interfaces, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_INTERFACES + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCComponents_Interfaces(); + +private: + virtual ~nsXPCComponents_Interfaces(); + + nsCOMArray mInterfaces; +}; + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_Interfaces) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_Interfaces"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetFlags(uint32_t* aFlags) +{ + // Mark ourselves as a DOM object so that instances may be created in + // unprivileged scopes. + *aFlags = nsIClassInfo::DOM_OBJECT; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Interfaces::nsXPCComponents_Interfaces() +{ +} + +nsXPCComponents_Interfaces::~nsXPCComponents_Interfaces() +{ + // empty +} + + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Interfaces) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Interfaces) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Interfaces) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_Interfaces) +NS_IMPL_RELEASE(nsXPCComponents_Interfaces) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Interfaces +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Interfaces" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_NEWENUMERATE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + + +NS_IMETHODIMP +nsXPCComponents_Interfaces::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::AutoIdVector& properties, + bool* _retval) +{ + + // Lazily init the list of interfaces when someone tries to + // enumerate them. + if (mInterfaces.IsEmpty()) { + XPTInterfaceInfoManager::GetSingleton()-> + GetScriptableInterfaces(mInterfaces); + } + + if (!properties.reserve(mInterfaces.Length())) { + *_retval = false; + return NS_OK; + } + + for (uint32_t index = 0; index < mInterfaces.Length(); index++) { + nsIInterfaceInfo* interface = mInterfaces.SafeElementAt(index); + if (!interface) + continue; + + const char* name; + if (NS_SUCCEEDED(interface->GetNameShared(&name)) && name) { + RootedString idstr(cx, JS_NewStringCopyZ(cx, name)); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + properties.infallibleAppend(id); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Interfaces::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + jsid idArg, bool* resolvedp, + bool* _retval) +{ + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + + if (!JSID_IS_STRING(id)) + return NS_OK; + + JSAutoByteString name; + RootedString str(cx, JSID_TO_STRING(id)); + + // we only allow interfaces by name here + if (name.encodeLatin1(cx, str) && name.ptr()[0] != '{') { + nsCOMPtr info = + ShimInterfaceInfo::MaybeConstruct(name.ptr(), cx); + if (!info) { + XPTInterfaceInfoManager::GetSingleton()-> + GetInfoForName(name.ptr(), getter_AddRefs(info)); + } + if (!info) + return NS_OK; + + nsCOMPtr nsid = nsJSIID::NewID(info); + + if (nsid) { + nsXPConnect* xpc = nsXPConnect::XPConnect(); + RootedObject idobj(cx); + if (NS_SUCCEEDED(xpc->WrapNative(cx, obj, + static_cast(nsid), + NS_GET_IID(nsIJSIID), + idobj.address()))) { + if (idobj) { + *resolvedp = true; + *_retval = JS_DefinePropertyById(cx, obj, id, idobj, + JSPROP_ENUMERATE | + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_RESOLVING); + } + } + } + } + return NS_OK; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class nsXPCComponents_InterfacesByID final : + public nsIXPCComponents_InterfacesByID, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_INTERFACESBYID + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCComponents_InterfacesByID(); + +private: + virtual ~nsXPCComponents_InterfacesByID(); + + nsCOMArray mInterfaces; +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_InterfacesByID) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_InterfacesByID"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetFlags(uint32_t* aFlags) +{ + // Mark ourselves as a DOM object so that instances may be created in + // unprivileged scopes. + *aFlags = nsIClassInfo::DOM_OBJECT; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_InterfacesByID::nsXPCComponents_InterfacesByID() +{ +} + +nsXPCComponents_InterfacesByID::~nsXPCComponents_InterfacesByID() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_InterfacesByID) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_InterfacesByID) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_InterfacesByID) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_InterfacesByID) +NS_IMPL_RELEASE(nsXPCComponents_InterfacesByID) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_InterfacesByID +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_InterfacesByID" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_NEWENUMERATE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::AutoIdVector& properties, + bool* _retval) +{ + + if (mInterfaces.IsEmpty()) { + XPTInterfaceInfoManager::GetSingleton()-> + GetScriptableInterfaces(mInterfaces); + } + + if (!properties.reserve(mInterfaces.Length())) { + *_retval = false; + return NS_OK; + } + + for (uint32_t index = 0; index < mInterfaces.Length(); index++) { + nsIInterfaceInfo* interface = mInterfaces.SafeElementAt(index); + if (!interface) + continue; + + nsIID const* iid; + if (NS_SUCCEEDED(interface->GetIIDShared(&iid))) { + char idstr[NSID_LENGTH]; + iid->ToProvidedString(idstr); + RootedString jsstr(cx, JS_NewStringCopyZ(cx, idstr)); + if (!jsstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, jsstr, &id)) { + *_retval = false; + return NS_OK; + } + + properties.infallibleAppend(id); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_InterfacesByID::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + jsid idArg, bool* resolvedp, + bool* _retval) +{ + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + + if (!JSID_IS_STRING(id)) + return NS_OK; + + RootedString str(cx, JSID_TO_STRING(id)); + if (38 != JS_GetStringLength(str)) + return NS_OK; + + JSAutoByteString utf8str; + if (utf8str.encodeUtf8(cx, str)) { + nsID iid; + if (!iid.Parse(utf8str.ptr())) + return NS_OK; + + nsCOMPtr info; + XPTInterfaceInfoManager::GetSingleton()-> + GetInfoForIID(&iid, getter_AddRefs(info)); + if (!info) + return NS_OK; + + nsCOMPtr nsid = nsJSIID::NewID(info); + + if (!nsid) + return NS_ERROR_OUT_OF_MEMORY; + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + RootedObject idobj(cx); + if (NS_SUCCEEDED(xpc->WrapNative(cx, obj, + static_cast(nsid), + NS_GET_IID(nsIJSIID), + idobj.address()))) { + if (idobj) { + *resolvedp = true; + *_retval = + JS_DefinePropertyById(cx, obj, id, idobj, + JSPROP_ENUMERATE | + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_RESOLVING); + } + } + } + return NS_OK; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + + + +class nsXPCComponents_Classes final : + public nsIXPCComponents_Classes, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_CLASSES + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCComponents_Classes(); + +private: + virtual ~nsXPCComponents_Classes(); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Classes::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_Classes) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_Classes"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetFlags(uint32_t* aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Classes::nsXPCComponents_Classes() +{ +} + +nsXPCComponents_Classes::~nsXPCComponents_Classes() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Classes) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Classes) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Classes) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_Classes) +NS_IMPL_RELEASE(nsXPCComponents_Classes) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Classes +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Classes" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_NEWENUMERATE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Classes::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::AutoIdVector& properties, + bool* _retval) +{ + nsCOMPtr compMgr; + if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) || !compMgr) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr e; + if (NS_FAILED(compMgr->EnumerateContractIDs(getter_AddRefs(e))) || !e) + return NS_ERROR_UNEXPECTED; + + bool hasMore; + nsCOMPtr isup; + while(NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore && + NS_SUCCEEDED(e->GetNext(getter_AddRefs(isup))) && isup) { + nsCOMPtr holder(do_QueryInterface(isup)); + if (!holder) + continue; + + nsAutoCString name; + if (NS_SUCCEEDED(holder->GetData(name))) { + RootedString idstr(cx, JS_NewStringCopyN(cx, name.get(), name.Length())); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + if (!properties.append(id)) { + *_retval = false; + return NS_OK; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Classes::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + jsid idArg, bool* resolvedp, + bool* _retval) + +{ + RootedId id(cx, idArg); + RootedObject obj(cx, objArg); + + JSAutoByteString name; + if (JSID_IS_STRING(id) && + name.encodeLatin1(cx, JSID_TO_STRING(id)) && + name.ptr()[0] != '{') { // we only allow contractids here + nsCOMPtr nsid = nsJSCID::NewID(name.ptr()); + if (nsid) { + nsXPConnect* xpc = nsXPConnect::XPConnect(); + RootedObject idobj(cx); + if (NS_SUCCEEDED(xpc->WrapNative(cx, obj, + static_cast(nsid), + NS_GET_IID(nsIJSCID), + idobj.address()))) { + if (idobj) { + *resolvedp = true; + *_retval = JS_DefinePropertyById(cx, obj, id, idobj, + JSPROP_ENUMERATE | + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_RESOLVING); + } + } + } + } + return NS_OK; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + +class nsXPCComponents_ClassesByID final : + public nsIXPCComponents_ClassesByID, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_CLASSESBYID + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCComponents_ClassesByID(); + +private: + virtual ~nsXPCComponents_ClassesByID(); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_ClassesByID) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_ClassesByID"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetFlags(uint32_t* aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_ClassesByID::nsXPCComponents_ClassesByID() +{ +} + +nsXPCComponents_ClassesByID::~nsXPCComponents_ClassesByID() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_ClassesByID) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_ClassesByID) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_ClassesByID) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_ClassesByID) +NS_IMPL_RELEASE(nsXPCComponents_ClassesByID) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_ClassesByID +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ClassesByID" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_NEWENUMERATE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::AutoIdVector& properties, + bool* _retval) +{ + + nsCOMPtr compMgr; + if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) || !compMgr) + return NS_ERROR_UNEXPECTED; + + nsISimpleEnumerator* e; + if (NS_FAILED(compMgr->EnumerateCIDs(&e)) || !e) + return NS_ERROR_UNEXPECTED; + + bool hasMore; + nsCOMPtr isup; + while(NS_SUCCEEDED(e->HasMoreElements(&hasMore)) && hasMore && + NS_SUCCEEDED(e->GetNext(getter_AddRefs(isup))) && isup) { + nsCOMPtr holder(do_QueryInterface(isup)); + if (!holder) + continue; + + char* name; + if (NS_SUCCEEDED(holder->ToString(&name)) && name) { + RootedString idstr(cx, JS_NewStringCopyZ(cx, name)); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + if (!properties.append(id)) { + *_retval = false; + return NS_OK; + } + } + } + + return NS_OK; +} + +static bool +IsRegisteredCLSID(const char* str) +{ + bool registered; + nsID id; + + if (!id.Parse(str)) + return false; + + nsCOMPtr compMgr; + if (NS_FAILED(NS_GetComponentRegistrar(getter_AddRefs(compMgr))) || !compMgr || + NS_FAILED(compMgr->IsCIDRegistered(id, ®istered))) + return false; + + return registered; +} + +NS_IMETHODIMP +nsXPCComponents_ClassesByID::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + jsid idArg, bool* resolvedp, + bool* _retval) +{ + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + + if (!JSID_IS_STRING(id)) + return NS_OK; + + JSAutoByteString name; + RootedString str(cx, JSID_TO_STRING(id)); + if (name.encodeLatin1(cx, str) && name.ptr()[0] == '{' && + IsRegisteredCLSID(name.ptr())) // we only allow canonical CLSIDs here + { + nsCOMPtr nsid = nsJSCID::NewID(name.ptr()); + if (nsid) { + nsXPConnect* xpc = nsXPConnect::XPConnect(); + RootedObject idobj(cx); + if (NS_SUCCEEDED(xpc->WrapNative(cx, obj, + static_cast(nsid), + NS_GET_IID(nsIJSCID), + idobj.address()))) { + if (idobj) { + *resolvedp = true; + *_retval = JS_DefinePropertyById(cx, obj, id, idobj, + JSPROP_ENUMERATE | + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_RESOLVING); + } + } + } + } + return NS_OK; +} + + +/***************************************************************************/ + +// Currently the possible results do not change at runtime, so they are only +// cached once (unlike ContractIDs, CLSIDs, and IIDs) + +class nsXPCComponents_Results final : + public nsIXPCComponents_Results, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_RESULTS + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCComponents_Results(); + +private: + virtual ~nsXPCComponents_Results(); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Results::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_Results) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_Results"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetFlags(uint32_t* aFlags) +{ + // Mark ourselves as a DOM object so that instances may be created in + // unprivileged scopes. + *aFlags = nsIClassInfo::DOM_OBJECT; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Results::nsXPCComponents_Results() +{ +} + +nsXPCComponents_Results::~nsXPCComponents_Results() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Results) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Results) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Results) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_Results) +NS_IMPL_RELEASE(nsXPCComponents_Results) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Results +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Results" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_NEWENUMERATE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Results::NewEnumerate(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + JS::AutoIdVector& properties, + bool* _retval) +{ + const char* name; + const void* iter = nullptr; + while (nsXPCException::IterateNSResults(nullptr, &name, nullptr, &iter)) { + RootedString idstr(cx, JS_NewStringCopyZ(cx, name)); + if (!idstr) { + *_retval = false; + return NS_OK; + } + + RootedId id(cx); + if (!JS_StringToId(cx, idstr, &id)) { + *_retval = false; + return NS_OK; + } + + if (!properties.append(id)) { + *_retval = false; + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Results::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + jsid idArg, bool* resolvedp, + bool* _retval) +{ + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + JSAutoByteString name; + + if (JSID_IS_STRING(id) && name.encodeLatin1(cx, JSID_TO_STRING(id))) { + const char* rv_name; + const void* iter = nullptr; + nsresult rv; + while (nsXPCException::IterateNSResults(&rv, &rv_name, nullptr, &iter)) { + if (!strcmp(name.ptr(), rv_name)) { + *resolvedp = true; + if (!JS_DefinePropertyById(cx, obj, id, (uint32_t)rv, + JSPROP_ENUMERATE | + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_RESOLVING)) { + return NS_ERROR_UNEXPECTED; + } + } + } + } + return NS_OK; +} + +/***************************************************************************/ +// JavaScript Constructor for nsIJSID objects (Components.ID) + +class nsXPCComponents_ID final : + public nsIXPCComponents_ID, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_ID + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + +public: + nsXPCComponents_ID(); + +private: + virtual ~nsXPCComponents_ID(); + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_ID::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_ID) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_ID"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetFlags(uint32_t* aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_ID::nsXPCComponents_ID() +{ +} + +nsXPCComponents_ID::~nsXPCComponents_ID() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_ID) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_ID) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_ID) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_ID) +NS_IMPL_RELEASE(nsXPCComponents_ID) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_ID +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_ID" +#define XPC_MAP_WANT_CALL +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_WANT_HASINSTANCE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + + +NS_IMETHODIMP +nsXPCComponents_ID::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_ID::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +// static +nsresult +nsXPCComponents_ID::CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) +{ + // make sure we have at least one arg + + if (args.length() < 1) + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + + // Do the security check if necessary + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, nsJSID::GetCID()))) { + // the security manager vetoed. It should have set an exception. + *_retval = false; + return NS_OK; + } + + // convert the first argument into a string and see if it looks like an id + + JSString* jsstr; + JSAutoByteString bytes; + nsID id; + + if (!(jsstr = ToString(cx, args[0])) || + !bytes.encodeLatin1(cx, jsstr) || + !id.Parse(bytes.ptr())) { + return ThrowAndFail(NS_ERROR_XPC_BAD_ID_STRING, cx, _retval); + } + + // make the new object and return it. + + JSObject* newobj = xpc_NewIDObject(cx, obj, id); + if (!newobj) + return NS_ERROR_UNEXPECTED; + + args.rval().setObject(*newobj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_ID::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* obj, + HandleValue val, bool* bp, bool* _retval) +{ + if (bp) + *bp = JSValIsInterfaceOfType(cx, val, NS_GET_IID(nsIJSID)); + return NS_OK; +} + +/***************************************************************************/ +// JavaScript Constructor for nsIXPCException objects (Components.Exception) + +class nsXPCComponents_Exception final : + public nsIXPCComponents_Exception, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_EXCEPTION + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + + +public: + nsXPCComponents_Exception(); + +private: + virtual ~nsXPCComponents_Exception(); + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Exception::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_Exception) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_Exception"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetFlags(uint32_t* aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Exception::nsXPCComponents_Exception() +{ +} + +nsXPCComponents_Exception::~nsXPCComponents_Exception() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Exception) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Exception) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Exception) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_Exception) +NS_IMPL_RELEASE(nsXPCComponents_Exception) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Exception +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Exception" +#define XPC_MAP_WANT_CALL +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_WANT_HASINSTANCE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + + +NS_IMETHODIMP +nsXPCComponents_Exception::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_Exception::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +struct MOZ_STACK_CLASS ExceptionArgParser +{ + ExceptionArgParser(JSContext* context, + nsXPConnect* xpconnect) + : eMsg("exception") + , eResult(NS_ERROR_FAILURE) + , cx(context) + , xpc(xpconnect) + {} + + // Public exception parameter values. During construction, these are + // initialized to the appropriate defaults. + const char* eMsg; + nsresult eResult; + nsCOMPtr eStack; + nsCOMPtr eData; + + // Parse the constructor arguments into the above |eFoo| parameter values. + bool parse(const CallArgs& args) { + /* + * The Components.Exception takes a series of arguments, all of them + * optional: + * + * Argument 0: Exception message (defaults to 'exception'). + * Argument 1: Result code (defaults to NS_ERROR_FAILURE) _or_ options + * object (see below). + * Argument 2: Stack (defaults to the current stack, which we trigger + * by leaving this nullptr in the parser). + * Argument 3: Optional user data (defaults to nullptr). + * + * To dig our way out of this clunky API, we now support passing an + * options object as the second parameter (as opposed to a result code). + * If this is the case, all subsequent arguments are ignored, and the + * following properties are parsed out of the object (using the + * associated default if the property does not exist): + * + * result: Result code (see argument 1). + * stack: Call stack (see argument 2). + * data: User data (see argument 3). + */ + if (args.length() > 0 && !parseMessage(args[0])) + return false; + if (args.length() > 1) { + if (args[1].isObject()) { + RootedObject obj(cx, &args[1].toObject()); + return parseOptionsObject(obj); + } + if (!parseResult(args[1])) + return false; + } + if (args.length() > 2) { + if (!parseStack(args[2])) + return false; + } + if (args.length() > 3) { + if (!parseData(args[3])) + return false; + } + return true; + } + + protected: + + /* + * Parsing helpers. + */ + + bool parseMessage(HandleValue v) { + JSString* str = ToString(cx, v); + if (!str) + return false; + eMsg = messageBytes.encodeLatin1(cx, str); + return !!eMsg; + } + + bool parseResult(HandleValue v) { + return JS::ToUint32(cx, v, (uint32_t*) &eResult); + } + + bool parseStack(HandleValue v) { + if (!v.isObject()) { + // eStack has already been initialized to null, which is what we want + // for any non-object values (including null). + return true; + } + + return NS_SUCCEEDED(xpc->WrapJS(cx, &v.toObject(), + NS_GET_IID(nsIStackFrame), + getter_AddRefs(eStack))); + } + + bool parseData(HandleValue v) { + if (!v.isObject()) { + // eData has already been initialized to null, which is what we want + // for any non-object values (including null). + return true; + } + + return NS_SUCCEEDED(xpc->WrapJS(cx, &v.toObject(), + NS_GET_IID(nsISupports), + getter_AddRefs(eData))); + } + + bool parseOptionsObject(HandleObject obj) { + RootedValue v(cx); + + if (!getOption(obj, "result", &v) || + (!v.isUndefined() && !parseResult(v))) + return false; + + if (!getOption(obj, "stack", &v) || + (!v.isUndefined() && !parseStack(v))) + return false; + + if (!getOption(obj, "data", &v) || + (!v.isUndefined() && !parseData(v))) + return false; + + return true; + } + + bool getOption(HandleObject obj, const char* name, MutableHandleValue rv) { + // Look for the property. + bool found; + if (!JS_HasProperty(cx, obj, name, &found)) + return false; + + // If it wasn't found, indicate with undefined. + if (!found) { + rv.setUndefined(); + return true; + } + + // Get the property. + return JS_GetProperty(cx, obj, name, rv); + } + + /* + * Internal data members. + */ + + // If there's a non-default exception string, hold onto the allocated bytes. + JSAutoByteString messageBytes; + + // Various bits and pieces that are helpful to have around. + JSContext* cx; + nsXPConnect* xpc; +}; + +// static +nsresult +nsXPCComponents_Exception::CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) +{ + nsXPConnect* xpc = nsXPConnect::XPConnect(); + + // Do the security check if necessary + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, Exception::GetCID()))) { + // the security manager vetoed. It should have set an exception. + *_retval = false; + return NS_OK; + } + + // Parse the arguments to the Exception constructor. + ExceptionArgParser parser(cx, xpc); + if (!parser.parse(args)) + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + + nsCOMPtr e = new Exception(nsCString(parser.eMsg), + parser.eResult, + EmptyCString(), + parser.eStack, + parser.eData); + + RootedObject newObj(cx); + if (NS_FAILED(xpc->WrapNative(cx, obj, e, NS_GET_IID(nsIXPCException), newObj.address())) || !newObj) { + return ThrowAndFail(NS_ERROR_XPC_CANT_CREATE_WN, cx, _retval); + } + + args.rval().setObject(*newObj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Exception::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext * cx, JSObject * obj, + HandleValue val, bool* bp, + bool* _retval) +{ + using namespace mozilla::dom; + + if (bp) { + *bp = (val.isObject() && + IS_INSTANCE_OF(Exception, &val.toObject())) || + JSValIsInterfaceOfType(cx, val, NS_GET_IID(nsIException)); + } + return NS_OK; +} + +/***************************************************************************/ +// This class is for the thing returned by "new Component.Constructor". + +// XXXjband we use this CID for security check, but security system can't see +// it since it has no registed factory. Security really kicks in when we try +// to build a wrapper around an instance. + +// {B4A95150-E25A-11d3-8F61-0010A4E73D9A} +#define NS_XPCCONSTRUCTOR_CID \ +{ 0xb4a95150, 0xe25a, 0x11d3, \ + { 0x8f, 0x61, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a } } + +class nsXPCConstructor : + public nsIXPCConstructor, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_XPCCONSTRUCTOR_CID) +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCONSTRUCTOR + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCConstructor() = delete; + nsXPCConstructor(nsIJSCID* aClassID, + nsIJSIID* aInterfaceID, + const char* aInitializer); + +private: + virtual ~nsXPCConstructor(); + nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +private: + RefPtr mClassID; + RefPtr mInterfaceID; + char* mInitializer; +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCConstructor::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCConstructor) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCConstructor::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCConstructor::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCConstructor::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCConstructor"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCConstructor::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCConstructor::GetFlags(uint32_t* aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCConstructor::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCConstructor::nsXPCConstructor(nsIJSCID* aClassID, + nsIJSIID* aInterfaceID, + const char* aInitializer) + : mClassID(aClassID), + mInterfaceID(aInterfaceID) +{ + mInitializer = aInitializer ? + (char*) nsMemory::Clone(aInitializer, strlen(aInitializer)+1) : + nullptr; +} + +nsXPCConstructor::~nsXPCConstructor() +{ + if (mInitializer) + free(mInitializer); +} + +NS_IMETHODIMP +nsXPCConstructor::GetClassID(nsIJSCID * *aClassID) +{ + RefPtr rval = mClassID; + rval.forget(aClassID); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCConstructor::GetInterfaceID(nsIJSIID * *aInterfaceID) +{ + RefPtr rval = mInterfaceID; + rval.forget(aInterfaceID); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCConstructor::GetInitializer(char * *aInitializer) +{ + XPC_STRING_GETTER_BODY(aInitializer, mInitializer); +} + +NS_INTERFACE_MAP_BEGIN(nsXPCConstructor) + NS_INTERFACE_MAP_ENTRY(nsIXPCConstructor) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCConstructor) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCConstructor) +NS_IMPL_RELEASE(nsXPCConstructor) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCConstructor +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCConstructor" +#define XPC_MAP_WANT_CALL +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_FLAGS 0 +#include "xpc_map_end.h" /* This will #undef the above */ + + +NS_IMETHODIMP +nsXPCConstructor::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); + +} + +NS_IMETHODIMP +nsXPCConstructor::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +// static +nsresult +nsXPCConstructor::CallOrConstruct(nsIXPConnectWrappedNative* wrapper,JSContext* cx, + HandleObject obj, const CallArgs& args, bool* _retval) +{ + nsXPConnect* xpc = nsXPConnect::XPConnect(); + + // security check not required because we are going to call through the + // code which is reflected into JS which will do that for us later. + + RootedObject cidObj(cx); + RootedObject iidObj(cx); + + if (NS_FAILED(xpc->WrapNative(cx, obj, mClassID, NS_GET_IID(nsIJSCID), cidObj.address())) || !cidObj || + NS_FAILED(xpc->WrapNative(cx, obj, mInterfaceID, NS_GET_IID(nsIJSIID), iidObj.address())) || !iidObj) { + return ThrowAndFail(NS_ERROR_XPC_CANT_CREATE_WN, cx, _retval); + } + + JS::Rooted arg(cx, ObjectValue(*iidObj)); + RootedValue rval(cx); + if (!JS_CallFunctionName(cx, cidObj, "createInstance", JS::HandleValueArray(arg), &rval) || + rval.isPrimitive()) { + // createInstance will have thrown an exception + *_retval = false; + return NS_OK; + } + + args.rval().set(rval); + + // call initializer method if supplied + if (mInitializer) { + RootedObject newObj(cx, &rval.toObject()); + // first check existence of function property for better error reporting + RootedValue fun(cx); + if (!JS_GetProperty(cx, newObj, mInitializer, &fun) || + fun.isPrimitive()) { + return ThrowAndFail(NS_ERROR_XPC_BAD_INITIALIZER_NAME, cx, _retval); + } + + RootedValue dummy(cx); + if (!JS_CallFunctionValue(cx, newObj, fun, args, &dummy)) { + // function should have thrown an exception + *_retval = false; + return NS_OK; + } + } + + return NS_OK; +} + +/*******************************************************/ +// JavaScript Constructor for nsIXPCConstructor objects (Components.Constructor) + +class nsXPCComponents_Constructor final : + public nsIXPCComponents_Constructor, + public nsIXPCScriptable, + public nsIClassInfo +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTS_CONSTRUCTOR + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSICLASSINFO + +public: + nsXPCComponents_Constructor(); + +private: + virtual ~nsXPCComponents_Constructor(); + static nsresult CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval); +}; + +/***************************************************************************/ +NS_IMETHODIMP +nsXPCComponents_Constructor::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCComponents_Constructor) + PUSH_IID(nsIXPCScriptable) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetScriptableHelper(nsIXPCScriptable** retval) +{ + *retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "XPCComponents_Constructor"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetFlags(uint32_t* aFlags) +{ + *aFlags = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +nsXPCComponents_Constructor::nsXPCComponents_Constructor() +{ +} + +nsXPCComponents_Constructor::~nsXPCComponents_Constructor() +{ + // empty +} + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Constructor) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Constructor) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Constructor) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_Constructor) +NS_IMPL_RELEASE(nsXPCComponents_Constructor) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Constructor +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Constructor" +#define XPC_MAP_WANT_CALL +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_WANT_HASINSTANCE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + + +NS_IMETHODIMP +nsXPCComponents_Constructor::Call(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::Construct(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + return CallOrConstruct(wrapper, cx, obj, args, _retval); +} + +// static +nsresult +nsXPCComponents_Constructor::CallOrConstruct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, HandleObject obj, + const CallArgs& args, bool* _retval) +{ + // make sure we have at least one arg + + if (args.length() < 1) + return ThrowAndFail(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx, _retval); + + // get the various other object pointers we need + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + XPCWrappedNativeScope* scope = ObjectScope(obj); + nsCOMPtr comp; + + if (!xpc || !scope || !(comp = do_QueryInterface(scope->GetComponents()))) + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + + // Do the security check if necessary + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, nsXPCConstructor::GetCID()))) { + // the security manager vetoed. It should have set an exception. + *_retval = false; + return NS_OK; + } + + // initialization params for the Constructor object we will create + nsCOMPtr cClassID; + nsCOMPtr cInterfaceID; + const char* cInitializer = nullptr; + JSAutoByteString cInitializerBytes; + + if (args.length() >= 3) { + // args[2] is an initializer function or property name + RootedString str(cx, ToString(cx, args[2])); + if (!str || !(cInitializer = cInitializerBytes.encodeLatin1(cx, str))) + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + } + + if (args.length() >= 2) { + // args[1] is an iid name string + // XXXjband support passing "Components.interfaces.foo"? + + nsCOMPtr ifaces; + RootedObject ifacesObj(cx); + + // we do the lookup by asking the Components.interfaces object + // for the property with this name - i.e. we let its caching of these + // nsIJSIID objects work for us. + + if (NS_FAILED(comp->GetInterfaces(getter_AddRefs(ifaces))) || + NS_FAILED(xpc->WrapNative(cx, obj, ifaces, + NS_GET_IID(nsIXPCComponents_Interfaces), + ifacesObj.address())) || !ifacesObj) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + + RootedString str(cx, ToString(cx, args[1])); + RootedId id(cx); + if (!str || !JS_StringToId(cx, str, &id)) + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + + RootedValue val(cx); + if (!JS_GetPropertyById(cx, ifacesObj, id, &val) || val.isPrimitive()) + return ThrowAndFail(NS_ERROR_XPC_BAD_IID, cx, _retval); + + nsCOMPtr wn; + if (NS_FAILED(xpc->GetWrappedNativeOfJSObject(cx, &val.toObject(), + getter_AddRefs(wn))) || !wn || + !(cInterfaceID = do_QueryWrappedNative(wn))) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + } else { + nsCOMPtr info; + xpc->GetInfoForIID(&NS_GET_IID(nsISupports), getter_AddRefs(info)); + + if (info) { + cInterfaceID = nsJSIID::NewID(info); + } + if (!cInterfaceID) + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + + // a new scope to avoid warnings about shadowed names + { + // argv[0] is a contractid name string + // XXXjband support passing "Components.classes.foo"? + + // we do the lookup by asking the Components.classes object + // for the property with this name - i.e. we let its caching of these + // nsIJSCID objects work for us. + + nsCOMPtr classes; + RootedObject classesObj(cx); + + if (NS_FAILED(comp->GetClasses(getter_AddRefs(classes))) || + NS_FAILED(xpc->WrapNative(cx, obj, classes, + NS_GET_IID(nsIXPCComponents_Classes), + classesObj.address())) || !classesObj) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + + RootedString str(cx, ToString(cx, args[0])); + RootedId id(cx); + if (!str || !JS_StringToId(cx, str, &id)) + return ThrowAndFail(NS_ERROR_XPC_BAD_CONVERT_JS, cx, _retval); + + RootedValue val(cx); + if (!JS_GetPropertyById(cx, classesObj, id, &val) || val.isPrimitive()) + return ThrowAndFail(NS_ERROR_XPC_BAD_CID, cx, _retval); + + nsCOMPtr wn; + if (NS_FAILED(xpc->GetWrappedNativeOfJSObject(cx, val.toObjectOrNull(), + getter_AddRefs(wn))) || !wn || + !(cClassID = do_QueryWrappedNative(wn))) { + return ThrowAndFail(NS_ERROR_XPC_UNEXPECTED, cx, _retval); + } + } + + nsCOMPtr ctor = new nsXPCConstructor(cClassID, cInterfaceID, cInitializer); + RootedObject newObj(cx); + + if (NS_FAILED(xpc->WrapNative(cx, obj, ctor, NS_GET_IID(nsIXPCConstructor), newObj.address())) || !newObj) { + return ThrowAndFail(NS_ERROR_XPC_CANT_CREATE_WN, cx, _retval); + } + + args.rval().setObject(*newObj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Constructor::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext * cx, JSObject * obj, + HandleValue val, bool* bp, + bool* _retval) +{ + if (bp) + *bp = JSValIsInterfaceOfType(cx, val, NS_GET_IID(nsIXPCConstructor)); + return NS_OK; +} + +class nsXPCComponents_Utils final : + public nsIXPCComponents_Utils, + public nsIXPCScriptable +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + NS_DECL_NSIXPCCOMPONENTS_UTILS + +public: + nsXPCComponents_Utils() { } + +private: + virtual ~nsXPCComponents_Utils() { } + nsCOMPtr mSandbox; +}; + +NS_INTERFACE_MAP_BEGIN(nsXPCComponents_Utils) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents_Utils) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCComponents_Utils) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsXPCComponents_Utils) +NS_IMPL_RELEASE(nsXPCComponents_Utils) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsXPCComponents_Utils +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents_Utils" +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +nsXPCComponents_Utils::GetSandbox(nsIXPCComponents_utils_Sandbox** aSandbox) +{ + NS_ENSURE_ARG_POINTER(aSandbox); + if (!mSandbox) + mSandbox = NewSandboxConstructor(); + + nsCOMPtr rval = mSandbox; + rval.forget(aSandbox); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ReportError(HandleValue error, JSContext* cx) +{ + // This function shall never fail! Silently eat any failure conditions. + + nsCOMPtr console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (!console) + return NS_OK; + + nsGlobalWindow* globalWin = CurrentWindowOrNull(cx); + nsPIDOMWindowInner* win = globalWin ? globalWin->AsInner() : nullptr; + const uint64_t innerWindowID = win ? win->WindowID() : 0; + + RootedObject errorObj(cx, error.isObject() ? &error.toObject() : nullptr); + JSErrorReport* err = errorObj ? JS_ErrorFromException(cx, errorObj) : nullptr; + + nsCOMPtr scripterr; + + if (errorObj) { + JS::RootedObject stackVal(cx, + FindExceptionStackForConsoleReport(win, error)); + if (stackVal) { + scripterr = new nsScriptErrorWithStack(stackVal); + } + } + + nsString fileName; + int32_t lineNo = 0; + + if (!scripterr) { + nsCOMPtr frame = dom::GetCurrentJSStack(); + if (frame) { + frame->GetFilename(cx, fileName); + frame->GetLineNumber(cx, &lineNo); + JS::Rooted stack(cx); + nsresult rv = frame->GetNativeSavedFrame(&stack); + if (NS_SUCCEEDED(rv) && stack.isObject()) { + JS::Rooted stackObj(cx, &stack.toObject()); + scripterr = new nsScriptErrorWithStack(stackObj); + } + } + } + + if (!scripterr) { + scripterr = new nsScriptError(); + } + + if (err) { + // It's a proper JS Error + nsAutoString fileUni; + CopyUTF8toUTF16(err->filename, fileUni); + + uint32_t column = err->tokenOffset(); + + const char16_t* linebuf = err->linebuf(); + + nsresult rv = scripterr->InitWithWindowID( + err->message() ? NS_ConvertUTF8toUTF16(err->message().c_str()) + : EmptyString(), + fileUni, + linebuf ? nsDependentString(linebuf, err->linebufLength()) : EmptyString(), + err->lineno, + column, err->flags, "XPConnect JavaScript", innerWindowID); + NS_ENSURE_SUCCESS(rv, NS_OK); + + console->LogMessage(scripterr); + return NS_OK; + } + + // It's not a JS Error object, so we synthesize as best we're able. + RootedString msgstr(cx, ToString(cx, error)); + if (!msgstr) + return NS_OK; + + nsAutoJSString msg; + if (!msg.init(cx, msgstr)) + return NS_OK; + + nsresult rv = scripterr->InitWithWindowID( + msg, fileName, EmptyString(), lineNo, 0, 0, + "XPConnect JavaScript", innerWindowID); + NS_ENSURE_SUCCESS(rv, NS_OK); + + console->LogMessage(scripterr); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::EvalInSandbox(const nsAString& source, + HandleValue sandboxVal, + HandleValue version, + const nsACString& filenameArg, + int32_t lineNumber, + JSContext* cx, + uint8_t optionalArgc, + MutableHandleValue retval) +{ + RootedObject sandbox(cx); + if (!JS_ValueToObject(cx, sandboxVal, &sandbox) || !sandbox) + return NS_ERROR_INVALID_ARG; + + // Optional third argument: JS version, as a string. + JSVersion jsVersion = JSVERSION_DEFAULT; + if (optionalArgc >= 1) { + JSString* jsVersionStr = ToString(cx, version); + if (!jsVersionStr) + return NS_ERROR_INVALID_ARG; + + JSAutoByteString bytes(cx, jsVersionStr); + if (!bytes) + return NS_ERROR_INVALID_ARG; + + jsVersion = JS_StringToVersion(bytes.ptr()); + // Explicitly check for "latest", which we support for sandboxes but + // isn't in the set of web-exposed version strings. + if (jsVersion == JSVERSION_UNKNOWN && + !strcmp(bytes.ptr(), "latest")) + { + jsVersion = JSVERSION_LATEST; + } + if (jsVersion == JSVERSION_UNKNOWN) + return NS_ERROR_INVALID_ARG; + } + + // Optional fourth and fifth arguments: filename and line number. + int32_t lineNo = (optionalArgc >= 3) ? lineNumber : 1; + nsCString filename; + if (!filenameArg.IsVoid()) { + filename.Assign(filenameArg); + } else { + // Get the current source info from xpc. + nsresult rv; + nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr frame; + xpc->GetCurrentJSStack(getter_AddRefs(frame)); + if (frame) { + nsString frameFile; + frame->GetFilename(cx, frameFile); + CopyUTF16toUTF8(frameFile, filename); + frame->GetLineNumber(cx, &lineNo); + } + } + + return xpc::EvalInSandbox(cx, sandbox, source, filename, lineNo, + jsVersion, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetSandboxAddonId(HandleValue sandboxVal, + JSContext* cx, MutableHandleValue rval) +{ + if (!sandboxVal.isObject()) + return NS_ERROR_INVALID_ARG; + + RootedObject sandbox(cx, &sandboxVal.toObject()); + sandbox = js::CheckedUnwrap(sandbox); + if (!sandbox || !xpc::IsSandbox(sandbox)) + return NS_ERROR_INVALID_ARG; + + return xpc::GetSandboxAddonId(cx, sandbox, rval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetSandboxMetadata(HandleValue sandboxVal, + JSContext* cx, MutableHandleValue rval) +{ + if (!sandboxVal.isObject()) + return NS_ERROR_INVALID_ARG; + + RootedObject sandbox(cx, &sandboxVal.toObject()); + sandbox = js::CheckedUnwrap(sandbox); + if (!sandbox || !xpc::IsSandbox(sandbox)) + return NS_ERROR_INVALID_ARG; + + return xpc::GetSandboxMetadata(cx, sandbox, rval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SetSandboxMetadata(HandleValue sandboxVal, + HandleValue metadataVal, + JSContext* cx) +{ + if (!sandboxVal.isObject()) + return NS_ERROR_INVALID_ARG; + + RootedObject sandbox(cx, &sandboxVal.toObject()); + sandbox = js::CheckedUnwrap(sandbox); + if (!sandbox || !xpc::IsSandbox(sandbox)) + return NS_ERROR_INVALID_ARG; + + nsresult rv = xpc::SetSandboxMetadata(cx, sandbox, metadataVal); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Import(const nsACString& registryLocation, + HandleValue targetObj, + JSContext* cx, + uint8_t optionalArgc, + MutableHandleValue retval) +{ + nsCOMPtr moduleloader = + do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID); + if (!moduleloader) + return NS_ERROR_FAILURE; + return moduleloader->Import(registryLocation, targetObj, cx, optionalArgc, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsModuleLoaded(const nsACString& registryLocation, bool* retval) +{ + nsCOMPtr moduleloader = + do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID); + if (!moduleloader) + return NS_ERROR_FAILURE; + return moduleloader->IsModuleLoaded(registryLocation, retval); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Unload(const nsACString & registryLocation) +{ + nsCOMPtr moduleloader = + do_GetService(MOZJSCOMPONENTLOADER_CONTRACTID); + if (!moduleloader) + return NS_ERROR_FAILURE; + return moduleloader->Unload(registryLocation); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ImportGlobalProperties(HandleValue aPropertyList, + JSContext* cx) +{ + RootedObject global(cx, CurrentGlobalOrNull(cx)); + MOZ_ASSERT(global); + + // Don't allow doing this if the global is a Window + nsGlobalWindow* win; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, &global, win))) { + return NS_ERROR_NOT_AVAILABLE; + } + + GlobalProperties options; + NS_ENSURE_TRUE(aPropertyList.isObject(), NS_ERROR_INVALID_ARG); + + RootedObject propertyList(cx, &aPropertyList.toObject()); + bool isArray; + if (NS_WARN_IF(!JS_IsArrayObject(cx, propertyList, &isArray))) { + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(!isArray)) { + return NS_ERROR_INVALID_ARG; + } + + if (!options.Parse(cx, propertyList) || + !options.DefineInXPCComponents(cx, global)) + { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetWeakReference(HandleValue object, JSContext* cx, + xpcIJSWeakReference** _retval) +{ + RefPtr ref = new xpcJSWeakReference(); + nsresult rv = ref->Init(cx, object); + NS_ENSURE_SUCCESS(rv, rv); + ref.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForceGC() +{ + JSContext* cx = nsXPConnect::GetContextInstance()->Context(); + PrepareForFullGC(cx); + GCForReason(cx, GC_NORMAL, gcreason::COMPONENT_UTILS); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForceCC(nsICycleCollectorListener* listener) +{ + nsJSContext::CycleCollectNow(listener); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::FinishCC() +{ + nsCycleCollector_finishAnyCurrentCollection(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CcSlice(int64_t budget) +{ + nsJSContext::RunCycleCollectorWorkSlice(budget); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetMaxCCSliceTimeSinceClear(int32_t* out) +{ + *out = nsJSContext::GetMaxCCSliceTimeSinceClear(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ClearMaxCCTime() +{ + nsJSContext::ClearMaxCCSliceTime(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForceShrinkingGC() +{ + JSContext* cx = dom::danger::GetJSContext(); + PrepareForFullGC(cx); + GCForReason(cx, GC_SHRINK, gcreason::COMPONENT_UTILS); + return NS_OK; +} + +class PreciseGCRunnable : public Runnable +{ + public: + PreciseGCRunnable(ScheduledGCCallback* aCallback, bool aShrinking) + : mCallback(aCallback), mShrinking(aShrinking) {} + + NS_IMETHOD Run() override + { + JSContext* cx = dom::danger::GetJSContext(); + if (JS_IsRunning(cx)) + return NS_DispatchToMainThread(this); + + nsJSContext::GarbageCollectNow(gcreason::COMPONENT_UTILS, + nsJSContext::NonIncrementalGC, + mShrinking ? + nsJSContext::ShrinkingGC : + nsJSContext::NonShrinkingGC); + + mCallback->Callback(); + return NS_OK; + } + + private: + RefPtr mCallback; + bool mShrinking; +}; + +NS_IMETHODIMP +nsXPCComponents_Utils::SchedulePreciseGC(ScheduledGCCallback* aCallback) +{ + RefPtr event = new PreciseGCRunnable(aCallback, false); + return NS_DispatchToMainThread(event); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SchedulePreciseShrinkingGC(ScheduledGCCallback* aCallback) +{ + RefPtr event = new PreciseGCRunnable(aCallback, true); + return NS_DispatchToMainThread(event); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::UnlinkGhostWindows() +{ +#ifdef DEBUG + nsWindowMemoryReporter::UnlinkGhostWindows(); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetJSTestingFunctions(JSContext* cx, + MutableHandleValue retval) +{ + JSObject* obj = js::GetTestingFunctions(cx); + if (!obj) + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + retval.setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CallFunctionWithAsyncStack(HandleValue function, + nsIStackFrame* stack, + const nsAString& asyncCause, + JSContext* cx, + MutableHandleValue retval) +{ + nsresult rv; + + if (!stack || asyncCause.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted asyncStack(cx); + rv = stack->GetNativeSavedFrame(&asyncStack); + if (NS_FAILED(rv)) + return rv; + if (!asyncStack.isObject()) { + JS_ReportErrorASCII(cx, "Must use a native JavaScript stack frame"); + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted asyncStackObj(cx, &asyncStack.toObject()); + + NS_ConvertUTF16toUTF8 utf8Cause(asyncCause); + JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, utf8Cause.get(), + JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT); + + if (!JS_CallFunctionValue(cx, nullptr, function, + JS::HandleValueArray::empty(), retval)) + { + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetGlobalForObject(HandleValue object, + JSContext* cx, + MutableHandleValue retval) +{ + // First argument must be an object. + if (object.isPrimitive()) + return NS_ERROR_XPC_BAD_CONVERT_JS; + + // Wrappers are parented to their the global in their home compartment. But + // when getting the global for a cross-compartment wrapper, we really want + // a wrapper for the foreign global. So we need to unwrap before getting the + // parent, enter the compartment for the duration of the call, and wrap the + // result. + Rooted obj(cx, &object.toObject()); + obj = js::UncheckedUnwrap(obj); + { + JSAutoCompartment ac(cx, obj); + obj = JS_GetGlobalForObject(cx, obj); + } + + if (!JS_WrapObject(cx, &obj)) + return NS_ERROR_FAILURE; + + // Get the WindowProxy if necessary. + obj = js::ToWindowProxyIfWindow(obj); + + retval.setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsProxy(HandleValue vobj, JSContext* cx, bool* rval) +{ + if (!vobj.isObject()) { + *rval = false; + return NS_OK; + } + + RootedObject obj(cx, &vobj.toObject()); + obj = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE); + + *rval = js::IsScriptedProxy(obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ExportFunction(HandleValue vfunction, HandleValue vscope, + HandleValue voptions, JSContext* cx, + MutableHandleValue rval) +{ + if (!xpc::ExportFunction(cx, vfunction, vscope, voptions, rval)) + return NS_ERROR_FAILURE; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CreateObjectIn(HandleValue vobj, HandleValue voptions, + JSContext* cx, MutableHandleValue rval) +{ + RootedObject optionsObject(cx, voptions.isObject() ? &voptions.toObject() + : nullptr); + CreateObjectInOptions options(cx, optionsObject); + if (voptions.isObject() && + !options.Parse()) + { + return NS_ERROR_FAILURE; + } + + if (!xpc::CreateObjectIn(cx, vobj, options, rval)) + return NS_ERROR_FAILURE; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::MakeObjectPropsNormal(HandleValue vobj, JSContext* cx) +{ + if (!cx) + return NS_ERROR_FAILURE; + + // first argument must be an object + if (vobj.isPrimitive()) + return NS_ERROR_XPC_BAD_CONVERT_JS; + + RootedObject obj(cx, js::UncheckedUnwrap(&vobj.toObject())); + JSAutoCompartment ac(cx, obj); + Rooted ida(cx, IdVector(cx)); + if (!JS_Enumerate(cx, obj, &ida)) + return NS_ERROR_FAILURE; + + RootedId id(cx); + RootedValue v(cx); + for (size_t i = 0; i < ida.length(); ++i) { + id = ida[i]; + + if (!JS_GetPropertyById(cx, obj, id, &v)) + return NS_ERROR_FAILURE; + + if (v.isPrimitive()) + continue; + + RootedObject propobj(cx, &v.toObject()); + // TODO Deal with non-functions. + if (!js::IsWrapper(propobj) || !JS::IsCallable(propobj)) + continue; + + FunctionForwarderOptions forwarderOptions; + if (!NewFunctionForwarder(cx, id, propobj, forwarderOptions, &v) || + !JS_SetPropertyById(cx, obj, id, v)) + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsDeadWrapper(HandleValue obj, bool* out) +{ + *out = false; + if (obj.isPrimitive()) + return NS_ERROR_INVALID_ARG; + + // Make sure to unwrap first. Once a proxy is nuked, it ceases to be a + // wrapper, meaning that, if passed to another compartment, we'll generate + // a CCW for it. Make sure that IsDeadWrapper sees through the confusion. + *out = JS_IsDeadWrapper(js::CheckedUnwrap(&obj.toObject())); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsCrossProcessWrapper(HandleValue obj, bool* out) +{ + *out = false; + if (obj.isPrimitive()) + return NS_ERROR_INVALID_ARG; + + *out = jsipc::IsWrappedCPOW(&obj.toObject()); + 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; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::PermitCPOWsInScope(HandleValue obj) +{ + if (!obj.isObject()) + return NS_ERROR_INVALID_ARG; + + JSObject* scopeObj = js::UncheckedUnwrap(&obj.toObject()); + CompartmentPrivate::Get(scopeObj)->allowCPOWs = true; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::RecomputeWrappers(HandleValue vobj, JSContext* cx) +{ + // Determine the compartment of the given object, if any. + JSCompartment* c = vobj.isObject() + ? js::GetObjectCompartment(js::UncheckedUnwrap(&vobj.toObject())) + : nullptr; + + // If no compartment was given, recompute all. + if (!c) + js::RecomputeWrappers(cx, js::AllCompartments(), js::AllCompartments()); + // Otherwise, recompute wrappers for the given compartment. + else + js::RecomputeWrappers(cx, js::SingleCompartment(c), js::AllCompartments()) && + js::RecomputeWrappers(cx, js::AllCompartments(), js::SingleCompartment(c)); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SetWantXrays(HandleValue vscope, JSContext* cx) +{ + if (!vscope.isObject()) + return NS_ERROR_INVALID_ARG; + JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject()); + JSCompartment* compartment = js::GetObjectCompartment(scopeObj); + CompartmentPrivate::Get(scopeObj)->wantXrays = true; + bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment), + js::AllCompartments()); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForcePermissiveCOWs(JSContext* cx) +{ + CrashIfNotInAutomation(); + CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->forcePermissiveCOWs = true; + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::ForcePrivilegedComponentsForScope(HandleValue vscope, + JSContext* cx) +{ + if (!vscope.isObject()) + return NS_ERROR_INVALID_ARG; + CrashIfNotInAutomation(); + JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject()); + XPCWrappedNativeScope* scope = ObjectScope(scopeObj); + scope->ForcePrivilegedComponents(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetComponentsForScope(HandleValue vscope, JSContext* cx, + MutableHandleValue rval) +{ + if (!vscope.isObject()) + return NS_ERROR_INVALID_ARG; + JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject()); + XPCWrappedNativeScope* scope = ObjectScope(scopeObj); + RootedObject components(cx); + if (!scope->GetComponentsJSObject(&components)) + return NS_ERROR_FAILURE; + if (!JS_WrapObject(cx, &components)) + return NS_ERROR_FAILURE; + rval.setObject(*components); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Dispatch(HandleValue runnableArg, HandleValue scope, + JSContext* cx) +{ + RootedValue runnable(cx, runnableArg); + // Enter the given compartment, if any, and rewrap runnable. + Maybe ac; + if (scope.isObject()) { + JSObject* scopeObj = js::UncheckedUnwrap(&scope.toObject()); + if (!scopeObj) + return NS_ERROR_FAILURE; + ac.emplace(cx, scopeObj); + if (!JS_WrapValue(cx, &runnable)) + return NS_ERROR_FAILURE; + } + + // Get an XPCWrappedJS for |runnable|. + if (!runnable.isObject()) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr run; + nsresult rv = nsXPConnect::XPConnect()->WrapJS(cx, &runnable.toObject(), + NS_GET_IID(nsIRunnable), + getter_AddRefs(run)); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(run); + + // Dispatch. + return NS_DispatchToMainThread(run); +} + +#define GENERATE_JSCONTEXTOPTION_GETTER_SETTER(_attr, _getter, _setter) \ + NS_IMETHODIMP \ + nsXPCComponents_Utils::Get## _attr(JSContext* cx, bool* aValue) \ + { \ + *aValue = ContextOptionsRef(cx)._getter(); \ + return NS_OK; \ + } \ + NS_IMETHODIMP \ + nsXPCComponents_Utils::Set## _attr(JSContext* cx, bool aValue) \ + { \ + ContextOptionsRef(cx)._setter(aValue); \ + return NS_OK; \ + } + +GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Strict, extraWarnings, setExtraWarnings) +GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Werror, werror, setWerror) +GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Strict_mode, strictMode, setStrictMode) +GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Ion, ion, setIon) + +#undef GENERATE_JSCONTEXTOPTION_GETTER_SETTER + +NS_IMETHODIMP +nsXPCComponents_Utils::SetGCZeal(int32_t aValue, JSContext* cx) +{ +#ifdef JS_GC_ZEAL + JS_SetGCZeal(cx, uint8_t(aValue), JS_DEFAULT_ZEAL_FREQ); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx) +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS); + NS_ENSURE_TRUE(obj.isObject(), NS_ERROR_INVALID_ARG); + JSObject* wrapper = &obj.toObject(); + NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG); + JSObject* sb = UncheckedUnwrap(wrapper); + NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG); + NukeCrossCompartmentWrappers(cx, AllCompartments(), + SingleCompartment(GetObjectCompartment(sb)), + NukeWindowReferences); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::BlockScriptForGlobal(HandleValue globalArg, + JSContext* cx) +{ + NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG); + RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(), + /* stopAtWindowProxy = */ false)); + NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG); + if (nsContentUtils::IsSystemPrincipal(xpc::GetObjectPrincipal(global))) { + JS_ReportErrorASCII(cx, "Script may not be disabled for system globals"); + return NS_ERROR_FAILURE; + } + Scriptability::Get(global).Block(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::UnblockScriptForGlobal(HandleValue globalArg, + JSContext* cx) +{ + NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG); + RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(), + /* stopAtWindowProxy = */ false)); + NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG); + if (nsContentUtils::IsSystemPrincipal(xpc::GetObjectPrincipal(global))) { + JS_ReportErrorASCII(cx, "Script may not be disabled for system globals"); + return NS_ERROR_FAILURE; + } + Scriptability::Get(global).Unblock(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::IsXrayWrapper(HandleValue obj, bool* aRetval) +{ + *aRetval = + obj.isObject() && xpc::WrapperFactory::IsXrayWrapper(&obj.toObject()); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::WaiveXrays(HandleValue aVal, JSContext* aCx, MutableHandleValue aRetval) +{ + RootedValue value(aCx, aVal); + if (!xpc::WrapperFactory::WaiveXrayAndWrap(aCx, &value)) + return NS_ERROR_FAILURE; + aRetval.set(value); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::UnwaiveXrays(HandleValue aVal, JSContext* aCx, MutableHandleValue aRetval) +{ + if (!aVal.isObject()) { + aRetval.set(aVal); + return NS_OK; + } + + RootedObject obj(aCx, js::UncheckedUnwrap(&aVal.toObject())); + if (!JS_WrapObject(aCx, &obj)) + return NS_ERROR_FAILURE; + aRetval.setObject(*obj); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetClassName(HandleValue aObj, bool aUnwrap, JSContext* aCx, char** aRv) +{ + if (!aObj.isObject()) + return NS_ERROR_INVALID_ARG; + RootedObject obj(aCx, &aObj.toObject()); + if (aUnwrap) + obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + *aRv = NS_strdup(js::GetObjectClass(obj)->name); + NS_ENSURE_TRUE(*aRv, NS_ERROR_OUT_OF_MEMORY); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetDOMClassInfo(const nsAString& aClassName, + nsIClassInfo** aClassInfo) +{ + *aClassInfo = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetIncumbentGlobal(HandleValue aCallback, + JSContext* aCx, MutableHandleValue aOut) +{ + nsCOMPtr global = mozilla::dom::GetIncumbentGlobal(); + RootedValue globalVal(aCx); + + if (!global) { + globalVal = NullValue(); + } else { + // Note: We rely on the wrap call for outerization. + globalVal = ObjectValue(*global->GetGlobalJSObject()); + if (!JS_WrapValue(aCx, &globalVal)) + return NS_ERROR_FAILURE; + } + + // Invoke the callback, if passed. + if (aCallback.isObject()) { + RootedValue ignored(aCx); + if (!JS_CallFunctionValue(aCx, nullptr, aCallback, JS::HandleValueArray(globalVal), &ignored)) + return NS_ERROR_FAILURE; + } + + aOut.set(globalVal); + return NS_OK; +} + +/* + * Below is a bunch of awkward junk to allow JS test code to trigger the + * creation of an XPCWrappedJS, such that it ends up in the map. We need to + * hand the caller some sort of reference to hold onto (to prevent the + * refcount from dropping to zero as soon as the function returns), but trying + * to return a bonafide XPCWrappedJS to script causes all sorts of trouble. So + * we create a benign holder class instead, which acts as an opaque reference + * that script can use to keep the XPCWrappedJS alive and in the map. + */ + +class WrappedJSHolder : public nsISupports +{ + NS_DECL_ISUPPORTS + WrappedJSHolder() {} + + RefPtr mWrappedJS; + +private: + virtual ~WrappedJSHolder() {} +}; +NS_IMPL_ISUPPORTS0(WrappedJSHolder); + +NS_IMETHODIMP +nsXPCComponents_Utils::GenerateXPCWrappedJS(HandleValue aObj, HandleValue aScope, + JSContext* aCx, nsISupports** aOut) +{ + if (!aObj.isObject()) + return NS_ERROR_INVALID_ARG; + RootedObject obj(aCx, &aObj.toObject()); + RootedObject scope(aCx, aScope.isObject() ? js::UncheckedUnwrap(&aScope.toObject()) + : CurrentGlobalOrNull(aCx)); + JSAutoCompartment ac(aCx, scope); + if (!JS_WrapObject(aCx, &obj)) + return NS_ERROR_FAILURE; + + RefPtr holder = new WrappedJSHolder(); + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(obj, NS_GET_IID(nsISupports), + getter_AddRefs(holder->mWrappedJS)); + holder.forget(aOut); + return rv; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetWatchdogTimestamp(const nsAString& aCategory, PRTime* aOut) +{ + WatchdogTimestampCategory category; + if (aCategory.EqualsLiteral("ContextStateChange")) + category = TimestampContextStateChange; + else if (aCategory.EqualsLiteral("WatchdogWakeup")) + category = TimestampWatchdogWakeup; + else if (aCategory.EqualsLiteral("WatchdogHibernateStart")) + category = TimestampWatchdogHibernateStart; + else if (aCategory.EqualsLiteral("WatchdogHibernateStop")) + category = TimestampWatchdogHibernateStop; + else + return NS_ERROR_INVALID_ARG; + *aOut = XPCJSContext::Get()->GetWatchdogTimestamp(category); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetJSEngineTelemetryValue(JSContext* cx, MutableHandleValue rval) +{ + RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) + return NS_ERROR_OUT_OF_MEMORY; + + unsigned attrs = JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT; + + size_t i = JS_SetProtoCalled(cx); + RootedValue v(cx, DoubleValue(i)); + if (!JS_DefineProperty(cx, obj, "setProto", v, attrs)) + return NS_ERROR_OUT_OF_MEMORY; + + i = JS_GetCustomIteratorCount(cx); + v.setDouble(i); + if (!JS_DefineProperty(cx, obj, "customIter", v, attrs)) + return NS_ERROR_OUT_OF_MEMORY; + + rval.setObject(*obj); + return NS_OK; +} + +bool +xpc::CloneInto(JSContext* aCx, HandleValue aValue, HandleValue aScope, + HandleValue aOptions, MutableHandleValue aCloned) +{ + if (!aScope.isObject()) + return false; + + RootedObject scope(aCx, &aScope.toObject()); + scope = js::CheckedUnwrap(scope); + if(!scope) { + JS_ReportErrorASCII(aCx, "Permission denied to clone object into scope"); + return false; + } + + if (!aOptions.isUndefined() && !aOptions.isObject()) { + JS_ReportErrorASCII(aCx, "Invalid argument"); + return false; + } + + RootedObject optionsObject(aCx, aOptions.isObject() ? &aOptions.toObject() + : nullptr); + StackScopedCloneOptions options(aCx, optionsObject); + if (aOptions.isObject() && !options.Parse()) + return false; + + { + JSAutoCompartment ac(aCx, scope); + aCloned.set(aValue); + if (!StackScopedClone(aCx, options, aCloned)) + return false; + } + + return JS_WrapValue(aCx, aCloned); +} + +NS_IMETHODIMP +nsXPCComponents_Utils::CloneInto(HandleValue aValue, HandleValue aScope, + HandleValue aOptions, JSContext* aCx, + MutableHandleValue aCloned) +{ + return xpc::CloneInto(aCx, aValue, aScope, aOptions, aCloned) ? + NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetWebIDLCallerPrincipal(nsIPrincipal** aResult) +{ + // This API may only be when the Entry Settings Object corresponds to a + // JS-implemented WebIDL call. In all other cases, the value will be null, + // and we throw. + nsCOMPtr callerPrin = mozilla::dom::GetWebIDLCallerPrincipal(); + if (!callerPrin) + return NS_ERROR_NOT_AVAILABLE; + callerPrin.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetObjectPrincipal(HandleValue val, JSContext* cx, + nsIPrincipal** result) +{ + if (!val.isObject()) + return NS_ERROR_INVALID_ARG; + RootedObject obj(cx, &val.toObject()); + obj = js::CheckedUnwrap(obj); + MOZ_ASSERT(obj); + + nsCOMPtr prin = nsContentUtils::ObjectPrincipal(obj); + prin.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::GetCompartmentLocation(HandleValue val, + JSContext* cx, + nsACString& result) +{ + if (!val.isObject()) + return NS_ERROR_INVALID_ARG; + RootedObject obj(cx, &val.toObject()); + obj = js::CheckedUnwrap(obj); + MOZ_ASSERT(obj); + + result = xpc::CompartmentPrivate::Get(obj)->GetLocation(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SetAddonInterposition(const nsACString& addonIdStr, + nsIAddonInterposition* interposition, + JSContext* cx) +{ + JSAddonId* addonId = xpc::NewAddonId(cx, addonIdStr); + if (!addonId) + return NS_ERROR_FAILURE; + if (!XPCWrappedNativeScope::SetAddonInterposition(cx, addonId, interposition)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::SetAddonCallInterposition(HandleValue target, + JSContext* cx) +{ + NS_ENSURE_TRUE(target.isObject(), NS_ERROR_INVALID_ARG); + RootedObject targetObj(cx, &target.toObject()); + targetObj = js::CheckedUnwrap(targetObj); + NS_ENSURE_TRUE(targetObj, NS_ERROR_INVALID_ARG); + XPCWrappedNativeScope* xpcScope = ObjectScope(targetObj); + NS_ENSURE_TRUE(xpcScope, NS_ERROR_INVALID_ARG); + + xpcScope->SetAddonCallInterposition(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::AllowCPOWsInAddon(const nsACString& addonIdStr, + bool allow, + JSContext* cx) +{ + JSAddonId* addonId = xpc::NewAddonId(cx, addonIdStr); + if (!addonId) + return NS_ERROR_FAILURE; + if (!XPCWrappedNativeScope::AllowCPOWsInAddon(cx, addonId, allow)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents_Utils::Now(double* aRetval) +{ + bool isInconsistent = false; + TimeStamp start = TimeStamp::ProcessCreation(isInconsistent); + *aRetval = (TimeStamp::Now() - start).ToMilliseconds(); + return NS_OK; +} + +/***************************************************************************/ +/***************************************************************************/ +/***************************************************************************/ + + +nsXPCComponentsBase::nsXPCComponentsBase(XPCWrappedNativeScope* aScope) + : mScope(aScope) +{ + MOZ_ASSERT(aScope, "aScope must not be null"); +} + +nsXPCComponents::nsXPCComponents(XPCWrappedNativeScope* aScope) + : nsXPCComponentsBase(aScope) +{ +} + +nsXPCComponentsBase::~nsXPCComponentsBase() +{ +} + +nsXPCComponents::~nsXPCComponents() +{ +} + +void +nsXPCComponentsBase::ClearMembers() +{ + mInterfaces = nullptr; + mInterfacesByID = nullptr; + mResults = nullptr; +} + +void +nsXPCComponents::ClearMembers() +{ + mClasses = nullptr; + mClassesByID = nullptr; + mID = nullptr; + mException = nullptr; + mConstructor = nullptr; + mUtils = nullptr; + + nsXPCComponentsBase::ClearMembers(); +} + +/*******************************************/ +#define XPC_IMPL_GET_OBJ_METHOD(_class, _n) \ +NS_IMETHODIMP _class::Get##_n(nsIXPCComponents_##_n * *a##_n) { \ + NS_ENSURE_ARG_POINTER(a##_n); \ + if (!m##_n) \ + m##_n = new nsXPCComponents_##_n(); \ + RefPtr ret = m##_n; \ + ret.forget(a##_n); \ + return NS_OK; \ +} + +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponentsBase, Interfaces) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponentsBase, InterfacesByID) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Classes) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, ClassesByID) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponentsBase, Results) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, ID) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Exception) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Constructor) +XPC_IMPL_GET_OBJ_METHOD(nsXPCComponents, Utils) + +#undef XPC_IMPL_GET_OBJ_METHOD +/*******************************************/ + +NS_IMETHODIMP +nsXPCComponentsBase::IsSuccessCode(nsresult result, bool* out) +{ + *out = NS_SUCCEEDED(result); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents::GetStack(nsIStackFrame * *aStack) +{ + nsresult rv; + nsXPConnect* xpc = nsXPConnect::XPConnect(); + rv = xpc->GetCurrentJSStack(aStack); + return rv; +} + +NS_IMETHODIMP +nsXPCComponents::GetManager(nsIComponentManager * *aManager) +{ + MOZ_ASSERT(aManager, "bad param"); + return NS_GetComponentManager(aManager); +} + +NS_IMETHODIMP +nsXPCComponents::GetReturnCode(JSContext* aCx, MutableHandleValue aOut) +{ + nsresult res = XPCJSContext::Get()->GetPendingResult(); + aOut.setNumber(static_cast(res)); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCComponents::SetReturnCode(JSContext* aCx, HandleValue aCode) +{ + nsresult rv; + if (!ToUint32(aCx, aCode, (uint32_t*)&rv)) + return NS_ERROR_FAILURE; + XPCJSContext::Get()->SetPendingResult(rv); + return NS_OK; +} + +// static +NS_IMETHODIMP nsXPCComponents::ReportError(HandleValue error, JSContext* cx) +{ + NS_WARNING("Components.reportError deprecated, use Components.utils.reportError"); + + nsCOMPtr utils; + nsresult rv = GetUtils(getter_AddRefs(utils)); + if (NS_FAILED(rv)) + return rv; + + return utils->ReportError(error, cx); +} + +/**********************************************/ + +class ComponentsSH : public nsIXPCScriptable +{ +public: + explicit constexpr ComponentsSH(unsigned dummy) + { + } + + // We don't actually inherit any ref counting infrastructure, but we don't + // need an nsAutoRefCnt member, so the _INHERITED macro is a hack to avoid + // having one. + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIXPCSCRIPTABLE + static nsresult Get(nsIXPCScriptable** helper) + { + *helper = &singleton; + return NS_OK; + } + +private: + static ComponentsSH singleton; +}; + +ComponentsSH ComponentsSH::singleton(0); + +// Singleton refcounting. +NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::AddRef(void) { return 1; } +NS_IMETHODIMP_(MozExternalRefCountType) ComponentsSH::Release(void) { return 1; } + +NS_INTERFACE_MAP_BEGIN(ComponentsSH) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +#define NSXPCCOMPONENTSBASE_CID \ +{ 0xc62998e5, 0x95f1, 0x4058, \ + { 0xa5, 0x09, 0xec, 0x21, 0x66, 0x18, 0x92, 0xb9 } } + +#define NSXPCCOMPONENTS_CID \ +{ 0x3649f405, 0xf0ec, 0x4c28, \ + { 0xae, 0xb0, 0xaf, 0x9a, 0x51, 0xe4, 0x4c, 0x81 } } + +NS_IMPL_CLASSINFO(nsXPCComponentsBase, &ComponentsSH::Get, nsIClassInfo::DOM_OBJECT, NSXPCCOMPONENTSBASE_CID) +NS_IMPL_ISUPPORTS_CI(nsXPCComponentsBase, nsIXPCComponentsBase) + +NS_IMPL_CLASSINFO(nsXPCComponents, &ComponentsSH::Get, nsIClassInfo::DOM_OBJECT, NSXPCCOMPONENTS_CID) +// Below is more or less what NS_IMPL_ISUPPORTS_CI_INHERITED1 would look like +// if it existed. +NS_IMPL_ADDREF_INHERITED(nsXPCComponents, nsXPCComponentsBase) +NS_IMPL_RELEASE_INHERITED(nsXPCComponents, nsXPCComponentsBase) +NS_INTERFACE_MAP_BEGIN(nsXPCComponents) + NS_INTERFACE_MAP_ENTRY(nsIXPCComponents) + NS_IMPL_QUERY_CLASSINFO(nsXPCComponents) +NS_INTERFACE_MAP_END_INHERITING(nsXPCComponentsBase) +NS_IMPL_CI_INTERFACE_GETTER(nsXPCComponents, nsIXPCComponents) + +// The nsIXPCScriptable map declaration that will generate stubs for us +#define XPC_MAP_CLASSNAME ComponentsSH +#define XPC_MAP_QUOTED_CLASSNAME "nsXPCComponents" +#define XPC_MAP_WANT_PRECREATE +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP +ComponentsSH::PreCreate(nsISupports* nativeObj, JSContext* cx, JSObject* globalObj, JSObject** parentObj) +{ + nsXPCComponentsBase* self = static_cast(nativeObj); + // this should never happen + if (!self->GetScope()) { + NS_WARNING("mScope must not be null when nsXPCComponents::PreCreate is called"); + return NS_ERROR_FAILURE; + } + *parentObj = self->GetScope()->GetGlobalJSObject(); + return NS_OK; +} diff --git a/js/xpconnect/src/XPCConvert.cpp b/js/xpconnect/src/XPCConvert.cpp new file mode 100644 index 000000000..37932b452 --- /dev/null +++ b/js/xpconnect/src/XPCConvert.cpp @@ -0,0 +1,1799 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Data conversion between native and JavaScript types. */ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Range.h" + +#include "xpcprivate.h" +#include "nsIAtom.h" +#include "nsWrapperCache.h" +#include "nsJSUtils.h" +#include "nsQueryObject.h" +#include "WrapperFactory.h" + +#include "nsWrapperCacheInlines.h" + +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/CharacterEncoding.h" +#include "jsprf.h" + +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" + +using namespace xpc; +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +//#define STRICT_CHECK_OF_UNICODE +#ifdef STRICT_CHECK_OF_UNICODE +#define ILLEGAL_RANGE(c) (0!=((c) & 0xFF80)) +#else // STRICT_CHECK_OF_UNICODE +#define ILLEGAL_RANGE(c) (0!=((c) & 0xFF00)) +#endif // STRICT_CHECK_OF_UNICODE + +#define ILLEGAL_CHAR_RANGE(c) (0!=((c) & 0x80)) + +/***********************************************************/ + +// static +bool +XPCConvert::IsMethodReflectable(const XPTMethodDescriptor& info) +{ + if (XPT_MD_IS_NOTXPCOM(info.flags) || XPT_MD_IS_HIDDEN(info.flags)) + return false; + + for (int i = info.num_args-1; i >= 0; i--) { + const nsXPTParamInfo& param = info.params[i]; + const nsXPTType& type = param.GetType(); + + // Reflected methods can't use native types. All native types end up + // getting tagged as void*, so this check is easy. + if (type.TagPart() == nsXPTType::T_VOID) + return false; + } + return true; +} + +static JSObject* +UnwrapNativeCPOW(nsISupports* wrapper) +{ + nsCOMPtr underware = do_QueryInterface(wrapper); + if (underware) { + JSObject* mainObj = underware->GetJSObject(); + if (mainObj && mozilla::jsipc::IsWrappedCPOW(mainObj)) + return mainObj; + } + return nullptr; +} + +/***************************************************************************/ + +// static +bool +XPCConvert::GetISupportsFromJSObject(JSObject* obj, nsISupports** iface) +{ + const JSClass* jsclass = js::GetObjectJSClass(obj); + MOZ_ASSERT(jsclass, "obj has no class"); + if (jsclass && + (jsclass->flags & JSCLASS_HAS_PRIVATE) && + (jsclass->flags & JSCLASS_PRIVATE_IS_NSISUPPORTS)) { + *iface = (nsISupports*) xpc_GetJSPrivate(obj); + return true; + } + *iface = UnwrapDOMObjectToISupports(obj); + return !!*iface; +} + +/***************************************************************************/ + +// static +bool +XPCConvert::NativeData2JS(MutableHandleValue d, const void* s, + const nsXPTType& type, const nsID* iid, nsresult* pErr) +{ + NS_PRECONDITION(s, "bad param"); + + AutoJSContext cx; + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + + switch (type.TagPart()) { + case nsXPTType::T_I8 : + d.setInt32(*static_cast(s)); + return true; + case nsXPTType::T_I16 : + d.setInt32(*static_cast(s)); + return true; + case nsXPTType::T_I32 : + d.setInt32(*static_cast(s)); + return true; + case nsXPTType::T_I64 : + d.setNumber(static_cast(*static_cast(s))); + return true; + case nsXPTType::T_U8 : + d.setInt32(*static_cast(s)); + return true; + case nsXPTType::T_U16 : + d.setInt32(*static_cast(s)); + return true; + case nsXPTType::T_U32 : + d.setNumber(*static_cast(s)); + return true; + case nsXPTType::T_U64 : + d.setNumber(static_cast(*static_cast(s))); + return true; + case nsXPTType::T_FLOAT : + d.setNumber(*static_cast(s)); + return true; + case nsXPTType::T_DOUBLE: + d.setNumber(*static_cast(s)); + return true; + case nsXPTType::T_BOOL : + d.setBoolean(*static_cast(s)); + return true; + case nsXPTType::T_CHAR : + { + char p = *static_cast(s); + +#ifdef STRICT_CHECK_OF_UNICODE + MOZ_ASSERT(! ILLEGAL_CHAR_RANGE(p) , "passing non ASCII data"); +#endif // STRICT_CHECK_OF_UNICODE + + JSString* str = JS_NewStringCopyN(cx, &p, 1); + if (!str) + return false; + + d.setString(str); + return true; + } + case nsXPTType::T_WCHAR : + { + char16_t p = *static_cast(s); + + JSString* str = JS_NewUCStringCopyN(cx, &p, 1); + if (!str) + return false; + + d.setString(str); + return true; + } + + case nsXPTType::T_JSVAL : + { + d.set(*static_cast(s)); + return JS_WrapValue(cx, d); + } + + case nsXPTType::T_VOID: + XPC_LOG_ERROR(("XPCConvert::NativeData2JS : void* params not supported")); + return false; + + case nsXPTType::T_IID: + { + nsID* iid2 = *static_cast(s); + if (!iid2) { + d.setNull(); + return true; + } + + RootedObject scope(cx, JS::CurrentGlobalOrNull(cx)); + JSObject* obj = xpc_NewIDObject(cx, scope, *iid2); + if (!obj) + return false; + + d.setObject(*obj); + return true; + } + + case nsXPTType::T_ASTRING: + // Fall through to T_DOMSTRING case + + case nsXPTType::T_DOMSTRING: + { + const nsAString* p = *static_cast(s); + if (!p || p->IsVoid()) { + d.setNull(); + return true; + } + + nsStringBuffer* buf; + if (!XPCStringConvert::ReadableToJSVal(cx, *p, &buf, d)) + return false; + if (buf) + buf->AddRef(); + return true; + } + + case nsXPTType::T_CHAR_STR: + { + const char* p = *static_cast(s); + if (!p) { + d.setNull(); + return true; + } + +#ifdef STRICT_CHECK_OF_UNICODE + bool isAscii = true; + for (char* t = p; *t && isAscii; t++) { + if (ILLEGAL_CHAR_RANGE(*t)) + isAscii = false; + } + MOZ_ASSERT(isAscii, "passing non ASCII data"); +#endif // STRICT_CHECK_OF_UNICODE + + JSString* str = JS_NewStringCopyZ(cx, p); + if (!str) + return false; + + d.setString(str); + return true; + } + + case nsXPTType::T_WCHAR_STR: + { + const char16_t* p = *static_cast(s); + if (!p) { + d.setNull(); + return true; + } + + JSString* str = JS_NewUCStringCopyZ(cx, p); + if (!str) + return false; + + d.setString(str); + return true; + } + case nsXPTType::T_UTF8STRING: + { + const nsACString* utf8String = *static_cast(s); + + if (!utf8String || utf8String->IsVoid()) { + d.setNull(); + return true; + } + + if (utf8String->IsEmpty()) { + d.set(JS_GetEmptyStringValue(cx)); + return true; + } + + const uint32_t len = CalcUTF8ToUnicodeLength(*utf8String); + // The cString is not empty at this point, but the calculated + // UTF-16 length is zero, meaning no valid conversion exists. + if (!len) + return false; + + const size_t buffer_size = (len + 1) * sizeof(char16_t); + char16_t* buffer = + static_cast(JS_malloc(cx, buffer_size)); + if (!buffer) + return false; + + uint32_t copied; + if (!UTF8ToUnicodeBuffer(*utf8String, buffer, &copied) || + len != copied) { + // Copy or conversion during copy failed. Did not copy the + // whole string. + JS_free(cx, buffer); + return false; + } + + // JS_NewUCString takes ownership on success, i.e. a + // successful call will make it the responsiblity of the JS VM + // to free the buffer. + JSString* str = JS_NewUCString(cx, buffer, len); + if (!str) { + JS_free(cx, buffer); + return false; + } + + d.setString(str); + return true; + } + case nsXPTType::T_CSTRING: + { + const nsACString* cString = *static_cast(s); + + if (!cString || cString->IsVoid()) { + d.setNull(); + return true; + } + + // c-strings (binary blobs) are deliberately not converted from + // UTF-8 to UTF-16. T_UTF8Sting is for UTF-8 encoded strings + // with automatic conversion. + JSString* str = JS_NewStringCopyN(cx, cString->Data(), + cString->Length()); + if (!str) + return false; + + d.setString(str); + return true; + } + + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + { + nsISupports* iface = *static_cast(s); + if (!iface) { + d.setNull(); + return true; + } + + if (iid->Equals(NS_GET_IID(nsIVariant))) { + nsCOMPtr variant = do_QueryInterface(iface); + if (!variant) + return false; + + return XPCVariant::VariantDataToJS(variant, + pErr, d); + } + + xpcObjectHelper helper(iface); + return NativeInterface2JSObject(d, nullptr, helper, iid, true, pErr); + } + + default: + NS_ERROR("bad type"); + return false; + } + return true; +} + +/***************************************************************************/ + +#ifdef DEBUG +static bool +CheckChar16InCharRange(char16_t c) +{ + if (ILLEGAL_RANGE(c)) { + /* U+0080/U+0100 - U+FFFF data lost. */ + static const size_t MSG_BUF_SIZE = 64; + char msg[MSG_BUF_SIZE]; + snprintf(msg, MSG_BUF_SIZE, "char16_t out of char range; high bits of data lost: 0x%x", int(c)); + NS_WARNING(msg); + return false; + } + + return true; +} + +template +static void +CheckCharsInCharRange(const CharT* chars, size_t len) +{ + for (size_t i = 0; i < len; i++) { + if (!CheckChar16InCharRange(chars[i])) + break; + } +} +#endif + +template +bool ConvertToPrimitive(JSContext* cx, HandleValue v, T* retval) +{ + return ValueToPrimitive(cx, v, retval); +} + +// static +bool +XPCConvert::JSData2Native(void* d, HandleValue s, + const nsXPTType& type, + const nsID* iid, + nsresult* pErr) +{ + NS_PRECONDITION(d, "bad param"); + + AutoJSContext cx; + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + switch (type.TagPart()) { + case nsXPTType::T_I8 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_I16 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_I32 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_I64 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_U8 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_U16 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_U32 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_U64 : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_FLOAT : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_DOUBLE : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_BOOL : + return ConvertToPrimitive(cx, s, static_cast(d)); + case nsXPTType::T_CHAR : + { + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + char16_t ch; + if (JS_GetStringLength(str) == 0) { + ch = 0; + } else { + if (!JS_GetStringCharAt(cx, str, 0, &ch)) + return false; + } +#ifdef DEBUG + CheckChar16InCharRange(ch); +#endif + *((char*)d) = char(ch); + break; + } + case nsXPTType::T_WCHAR : + { + JSString* str; + if (!(str = ToString(cx, s))) { + return false; + } + size_t length = JS_GetStringLength(str); + if (length == 0) { + *((uint16_t*)d) = 0; + break; + } + + char16_t ch; + if (!JS_GetStringCharAt(cx, str, 0, &ch)) + return false; + + *((uint16_t*)d) = uint16_t(ch); + break; + } + case nsXPTType::T_JSVAL : + *((Value*)d) = s; + break; + case nsXPTType::T_VOID: + XPC_LOG_ERROR(("XPCConvert::JSData2Native : void* params not supported")); + NS_ERROR("void* params not supported"); + return false; + case nsXPTType::T_IID: + { + const nsID* pid = nullptr; + + // There's no good reason to pass a null IID. + if (s.isNullOrUndefined()) { + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + return false; + } + + if (!s.isObject() || + (!(pid = xpc_JSObjectToID(cx, &s.toObject()))) || + (!(pid = (const nsID*) nsMemory::Clone(pid, sizeof(nsID))))) { + return false; + } + *((const nsID**)d) = pid; + return true; + } + + case nsXPTType::T_ASTRING: + { + if (s.isUndefined()) { + (**((nsAString**)d)).SetIsVoid(true); + return true; + } + MOZ_FALLTHROUGH; + } + case nsXPTType::T_DOMSTRING: + { + if (s.isNull()) { + (**((nsAString**)d)).SetIsVoid(true); + return true; + } + size_t length = 0; + JSString* str = nullptr; + if (!s.isUndefined()) { + str = ToString(cx, s); + if (!str) + return false; + + length = JS_GetStringLength(str); + if (!length) { + (**((nsAString**)d)).Truncate(); + return true; + } + } + + nsAString* ws = *((nsAString**)d); + + if (!str) { + ws->AssignLiteral(u"undefined"); + } else if (XPCStringConvert::IsDOMString(str)) { + // The characters represent an existing nsStringBuffer that + // was shared by XPCStringConvert::ReadableToJSVal. + const char16_t* chars = JS_GetTwoByteExternalStringChars(str); + if (chars[length] == '\0') { + // Safe to share the buffer. + nsStringBuffer::FromData((void*)chars)->ToString(length, *ws); + } else { + // We have to copy to ensure null-termination. + ws->Assign(chars, length); + } + } else if (XPCStringConvert::IsLiteral(str)) { + // The characters represent a literal char16_t string constant + // compiled into libxul, such as the string "undefined" above. + const char16_t* chars = JS_GetTwoByteExternalStringChars(str); + ws->AssignLiteral(chars, length); + } else { + if (!AssignJSString(cx, *ws, str)) + return false; + } + return true; + } + + case nsXPTType::T_CHAR_STR: + { + if (s.isUndefined() || s.isNull()) { + *((char**)d) = nullptr; + return true; + } + + JSString* str = ToString(cx, s); + if (!str) { + return false; + } +#ifdef DEBUG + if (JS_StringHasLatin1Chars(str)) { + size_t len; + AutoCheckCannotGC nogc; + const Latin1Char* chars = JS_GetLatin1StringCharsAndLength(cx, nogc, str, &len); + if (chars) + CheckCharsInCharRange(chars, len); + } else { + size_t len; + AutoCheckCannotGC nogc; + const char16_t* chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, str, &len); + if (chars) + CheckCharsInCharRange(chars, len); + } +#endif // DEBUG + size_t length = JS_GetStringEncodingLength(cx, str); + if (length == size_t(-1)) { + return false; + } + char* buffer = static_cast(moz_xmalloc(length + 1)); + if (!buffer) { + return false; + } + JS_EncodeStringToBuffer(cx, str, buffer, length); + buffer[length] = '\0'; + *((void**)d) = buffer; + return true; + } + + case nsXPTType::T_WCHAR_STR: + { + JSString* str; + + if (s.isUndefined() || s.isNull()) { + *((char16_t**)d) = nullptr; + return true; + } + + if (!(str = ToString(cx, s))) { + return false; + } + int len = JS_GetStringLength(str); + int byte_len = (len+1)*sizeof(char16_t); + if (!(*((void**)d) = moz_xmalloc(byte_len))) { + // XXX should report error + return false; + } + mozilla::Range destChars(*((char16_t**)d), len + 1); + if (!JS_CopyStringChars(cx, destChars, str)) + return false; + destChars[len] = 0; + + return true; + } + + case nsXPTType::T_UTF8STRING: + { + if (s.isNull() || s.isUndefined()) { + nsCString* rs = *((nsCString**)d); + rs->SetIsVoid(true); + return true; + } + + // The JS val is neither null nor void... + JSString* str = ToString(cx, s); + if (!str) + return false; + + size_t length = JS_GetStringLength(str); + if (!length) { + nsCString* rs = *((nsCString**)d); + rs->Truncate(); + return true; + } + + JSFlatString* flat = JS_FlattenString(cx, str); + if (!flat) + return false; + + size_t utf8Length = JS::GetDeflatedUTF8StringLength(flat); + nsACString* rs = *((nsACString**)d); + rs->SetLength(utf8Length); + + JS::DeflateStringToUTF8Buffer(flat, mozilla::RangedPtr(rs->BeginWriting(), utf8Length)); + + return true; + } + + case nsXPTType::T_CSTRING: + { + if (s.isNull() || s.isUndefined()) { + nsACString* rs = *((nsACString**)d); + rs->SetIsVoid(true); + return true; + } + + // The JS val is neither null nor void... + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + size_t length = JS_GetStringEncodingLength(cx, str); + if (length == size_t(-1)) { + return false; + } + + if (!length) { + nsCString* rs = *((nsCString**)d); + rs->Truncate(); + return true; + } + + nsACString* rs = *((nsACString**)d); + rs->SetLength(uint32_t(length)); + if (rs->Length() != uint32_t(length)) { + return false; + } + JS_EncodeStringToBuffer(cx, str, rs->BeginWriting(), length); + + return true; + } + + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + { + MOZ_ASSERT(iid,"can't do interface conversions without iid"); + + if (iid->Equals(NS_GET_IID(nsIVariant))) { + nsCOMPtr variant = XPCVariant::newVariant(cx, s); + if (!variant) + return false; + + variant.forget(static_cast(d)); + return true; + } else if (iid->Equals(NS_GET_IID(nsIAtom)) && s.isString()) { + // We're trying to pass a string as an nsIAtom. Let's atomize! + JSString* str = s.toString(); + nsAutoJSString autoStr; + if (!autoStr.init(cx, str)) { + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF; + return false; + } + nsCOMPtr atom = NS_Atomize(autoStr); + atom.forget((nsISupports**)d); + return true; + } + //else ... + + if (s.isNullOrUndefined()) { + *((nsISupports**)d) = nullptr; + return true; + } + + // only wrap JSObjects + if (!s.isObject()) { + if (pErr && s.isInt32() && 0 == s.toInt32()) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL; + return false; + } + + RootedObject src(cx, &s.toObject()); + return JSObject2NativeInterface((void**)d, src, iid, nullptr, pErr); + } + default: + NS_ERROR("bad type"); + return false; + } + return true; +} + +static inline bool +CreateHolderIfNeeded(HandleObject obj, MutableHandleValue d, + nsIXPConnectJSObjectHolder** dest) +{ + if (dest) { + if (!obj) + return false; + RefPtr objHolder = new XPCJSObjectHolder(obj); + objHolder.forget(dest); + } + + d.setObjectOrNull(obj); + + return true; +} + +/***************************************************************************/ +// static +bool +XPCConvert::NativeInterface2JSObject(MutableHandleValue d, + nsIXPConnectJSObjectHolder** dest, + xpcObjectHelper& aHelper, + const nsID* iid, + bool allowNativeWrapper, + nsresult* pErr) +{ + if (!iid) + iid = &NS_GET_IID(nsISupports); + + d.setNull(); + if (dest) + *dest = nullptr; + if (!aHelper.Object()) + return true; + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + + // We used to have code here that unwrapped and simply exposed the + // underlying JSObject. That caused anomolies when JSComponents were + // accessed from other JS code - they didn't act like other xpconnect + // wrapped components. So, instead, we create "double wrapped" objects + // (that means an XPCWrappedNative around an nsXPCWrappedJS). This isn't + // optimal -- we could detect this and roll the functionality into a + // single wrapper, but the current solution is good enough for now. + AutoJSContext cx; + XPCWrappedNativeScope* xpcscope = ObjectScope(JS::CurrentGlobalOrNull(cx)); + if (!xpcscope) + return false; + + // First, see if this object supports the wrapper cache. + // Note: If |cache->IsDOMBinding()| is true, then it means that the object + // implementing it doesn't want a wrapped native as its JS Object, but + // instead it provides its own proxy object. In that case, the object + // to use is found as cache->GetWrapper(). If that is null, then the + // object will create (and fill the cache) from its WrapObject call. + nsWrapperCache* cache = aHelper.GetWrapperCache(); + + RootedObject flat(cx, cache ? cache->GetWrapper() : nullptr); + if (!flat && cache && cache->IsDOMBinding()) { + RootedObject global(cx, xpcscope->GetGlobalJSObject()); + js::AssertSameCompartment(cx, global); + flat = cache->WrapObject(cx, nullptr); + if (!flat) + return false; + } + if (flat) { + if (allowNativeWrapper && !JS_WrapObject(cx, &flat)) + return false; + return CreateHolderIfNeeded(flat, d, dest); + } + +#ifdef SPIDERMONKEY_PROMISE + if (iid->Equals(NS_GET_IID(nsISupports))) { + // Check for a Promise being returned via nsISupports. In that + // situation, we want to dig out its underlying JS object and return + // that. + RefPtr promise = do_QueryObject(aHelper.Object()); + if (promise) { + flat = promise->PromiseObj(); + if (!JS_WrapObject(cx, &flat)) + return false; + return CreateHolderIfNeeded(flat, d, dest); + } + } +#endif // SPIDERMONKEY_PROMISE + + // Don't double wrap CPOWs. This is a temporary measure for compatibility + // with objects that don't provide necessary QIs (such as objects under + // the new DOM bindings). We expect the other side of the CPOW to have + // the appropriate wrappers in place. + RootedObject cpow(cx, UnwrapNativeCPOW(aHelper.Object())); + if (cpow) { + if (!JS_WrapObject(cx, &cpow)) + return false; + d.setObject(*cpow); + return true; + } + + // Go ahead and create an XPCWrappedNative for this object. + RefPtr iface = + XPCNativeInterface::GetNewOrUsed(iid); + if (!iface) + return false; + + RefPtr wrapper; + nsresult rv = XPCWrappedNative::GetNewOrUsed(aHelper, xpcscope, iface, + getter_AddRefs(wrapper)); + if (NS_FAILED(rv) && pErr) + *pErr = rv; + + // If creating the wrapped native failed, then return early. + if (NS_FAILED(rv) || !wrapper) + return false; + + // If we're not creating security wrappers, we can return the + // XPCWrappedNative as-is here. + flat = wrapper->GetFlatJSObject(); + if (!allowNativeWrapper) { + d.setObjectOrNull(flat); + if (dest) + wrapper.forget(dest); + if (pErr) + *pErr = NS_OK; + return true; + } + + // The call to wrap here handles both cross-compartment and same-compartment + // security wrappers. + RootedObject original(cx, flat); + if (!JS_WrapObject(cx, &flat)) + return false; + + d.setObjectOrNull(flat); + + if (dest) { + // The wrapper still holds the original flat object. + if (flat == original) { + wrapper.forget(dest); + } else { + if (!flat) + return false; + RefPtr objHolder = new XPCJSObjectHolder(flat); + objHolder.forget(dest); + } + } + + if (pErr) + *pErr = NS_OK; + + return true; +} + +/***************************************************************************/ + +// static +bool +XPCConvert::JSObject2NativeInterface(void** dest, HandleObject src, + const nsID* iid, + nsISupports* aOuter, + nsresult* pErr) +{ + MOZ_ASSERT(dest, "bad param"); + MOZ_ASSERT(src, "bad param"); + MOZ_ASSERT(iid, "bad param"); + + AutoJSContext cx; + JSAutoCompartment ac(cx, src); + + *dest = nullptr; + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + nsISupports* iface; + + if (!aOuter) { + // Note that if we have a non-null aOuter then it means that we are + // forcing the creation of a wrapper even if the object *is* a + // wrappedNative or other wise has 'nsISupportness'. + // This allows wrapJSAggregatedToNative to work. + + // If we're looking at a security wrapper, see now if we're allowed to + // pass it to C++. If we are, then fall through to the code below. If + // we aren't, throw an exception eagerly. + // + // NB: It's very important that we _don't_ unwrap in the aOuter case, + // because the caller may explicitly want to create the XPCWrappedJS + // around a security wrapper. XBL does this with Xrays from the XBL + // scope - see nsBindingManager::GetBindingImplementation. + // + // It's also very important that "inner" be rooted here. + RootedObject inner(cx, + js::CheckedUnwrap(src, + /* stopAtWindowProxy = */ false)); + if (!inner) { + if (pErr) + *pErr = NS_ERROR_XPC_SECURITY_MANAGER_VETO; + return false; + } + + // Is this really a native xpcom object with a wrapper? + XPCWrappedNative* wrappedNative = nullptr; + if (IS_WN_REFLECTOR(inner)) + wrappedNative = XPCWrappedNative::Get(inner); + if (wrappedNative) { + iface = wrappedNative->GetIdentityObject(); + return NS_SUCCEEDED(iface->QueryInterface(*iid, dest)); + } + // else... + + // Deal with slim wrappers here. + if (GetISupportsFromJSObject(inner ? inner : src, &iface)) { + if (iface && NS_SUCCEEDED(iface->QueryInterface(*iid, dest))) { + return true; + } + + // If that failed, and iid is for mozIDOMWindowProxy, we actually + // want the outer! + if (iid->Equals(NS_GET_IID(mozIDOMWindowProxy))) { + if (nsCOMPtr inner = do_QueryInterface(iface)) { + iface = nsPIDOMWindowInner::From(inner)->GetOuterWindow(); + return NS_SUCCEEDED(iface->QueryInterface(*iid, dest)); + } + } + + return false; + } + +#ifdef SPIDERMONKEY_PROMISE + // Deal with Promises being passed as nsISupports. In that situation we + // want to create a dom::Promise and use that. + if (iid->Equals(NS_GET_IID(nsISupports))) { + RootedObject innerObj(cx, inner); + if (IsPromiseObject(innerObj)) { + nsIGlobalObject* glob = NativeGlobal(innerObj); + RefPtr p = Promise::CreateFromExisting(glob, innerObj); + return p && NS_SUCCEEDED(p->QueryInterface(*iid, dest)); + } + } +#endif // SPIDERMONKEY_PROMISE + } + + RefPtr wrapper; + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(src, *iid, getter_AddRefs(wrapper)); + if (pErr) + *pErr = rv; + + if (NS_FAILED(rv) || !wrapper) + return false; + + // If the caller wanted to aggregate this JS object to a native, + // attach it to the wrapper. Note that we allow a maximum of one + // aggregated native for a given XPCWrappedJS. + if (aOuter) + wrapper->SetAggregatedNativeObject(aOuter); + + // We need to go through the QueryInterface logic to make this return + // the right thing for the various 'special' interfaces; e.g. + // nsIPropertyBag. We must use AggregatedQueryInterface in cases where + // there is an outer to avoid nasty recursion. + rv = aOuter ? wrapper->AggregatedQueryInterface(*iid, dest) : + wrapper->QueryInterface(*iid, dest); + if (pErr) + *pErr = rv; + return NS_SUCCEEDED(rv); +} + +/***************************************************************************/ +/***************************************************************************/ + +// static +nsresult +XPCConvert::ConstructException(nsresult rv, const char* message, + const char* ifaceName, const char* methodName, + nsISupports* data, + nsIException** exceptn, + JSContext* cx, + Value* jsExceptionPtr) +{ + MOZ_ASSERT(!cx == !jsExceptionPtr, "Expected cx and jsExceptionPtr to cooccur."); + + static const char format[] = "\'%s\' when calling method: [%s::%s]"; + const char * msg = message; + nsXPIDLString xmsg; + nsAutoCString sxmsg; + + nsCOMPtr errorObject = do_QueryInterface(data); + if (errorObject) { + if (NS_SUCCEEDED(errorObject->GetMessageMoz(getter_Copies(xmsg)))) { + CopyUTF16toUTF8(xmsg, sxmsg); + msg = sxmsg.get(); + } + } + if (!msg) + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &msg) || ! msg) + msg = ""; + + nsCString msgStr(msg); + if (ifaceName && methodName) + msgStr.AppendPrintf(format, msg, ifaceName, methodName); + + RefPtr e = new Exception(msgStr, rv, EmptyCString(), nullptr, data); + + if (cx && jsExceptionPtr) { + e->StowJSVal(*jsExceptionPtr); + } + + e.forget(exceptn); + return NS_OK; +} + +/********************************/ + +class MOZ_STACK_CLASS AutoExceptionRestorer +{ +public: + AutoExceptionRestorer(JSContext* cx, const Value& v) + : mContext(cx), tvr(cx, v) + { + JS_ClearPendingException(mContext); + } + + ~AutoExceptionRestorer() + { + JS_SetPendingException(mContext, tvr); + } + +private: + JSContext * const mContext; + RootedValue tvr; +}; + +static nsresult +JSErrorToXPCException(const char* toStringResult, + const char* ifaceName, + const char* methodName, + const JSErrorReport* report, + nsIException** exceptn) +{ + AutoJSContext cx; + nsresult rv = NS_ERROR_FAILURE; + RefPtr data; + if (report) { + nsAutoString bestMessage; + if (report && report->message()) { + CopyUTF8toUTF16(report->message().c_str(), bestMessage); + } else if (toStringResult) { + CopyUTF8toUTF16(toStringResult, bestMessage); + } else { + bestMessage.AssignLiteral("JavaScript Error"); + } + + const char16_t* linebuf = report->linebuf(); + + data = new nsScriptError(); + data->InitWithWindowID( + bestMessage, + NS_ConvertASCIItoUTF16(report->filename), + linebuf ? nsDependentString(linebuf, report->linebufLength()) : EmptyString(), + report->lineno, + report->tokenOffset(), report->flags, + NS_LITERAL_CSTRING("XPConnect JavaScript"), + nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx)); + } + + if (data) { + nsAutoCString formattedMsg; + data->ToString(formattedMsg); + + rv = XPCConvert::ConstructException(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, + formattedMsg.get(), ifaceName, + methodName, + static_cast(data.get()), + exceptn, nullptr, nullptr); + } else { + rv = XPCConvert::ConstructException(NS_ERROR_XPC_JAVASCRIPT_ERROR, + nullptr, ifaceName, methodName, + nullptr, exceptn, nullptr, nullptr); + } + return rv; +} + +// static +nsresult +XPCConvert::JSValToXPCException(MutableHandleValue s, + const char* ifaceName, + const char* methodName, + nsIException** exceptn) +{ + AutoJSContext cx; + AutoExceptionRestorer aer(cx, s); + + if (!s.isPrimitive()) { + // we have a JSObject + RootedObject obj(cx, s.toObjectOrNull()); + + if (!obj) { + NS_ERROR("when is an object not an object?"); + return NS_ERROR_FAILURE; + } + + // is this really a native xpcom object with a wrapper? + JSObject* unwrapped = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + if (!unwrapped) + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + if (nsCOMPtr supports = UnwrapReflectorToISupports(unwrapped)) { + nsCOMPtr iface = do_QueryInterface(supports); + if (iface) { + // just pass through the exception (with extra ref and all) + nsCOMPtr temp = iface; + temp.forget(exceptn); + return NS_OK; + } else { + // it is a wrapped native, but not an exception! + return ConstructException(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT, + nullptr, ifaceName, methodName, supports, + exceptn, nullptr, nullptr); + } + } else { + // It is a JSObject, but not a wrapped native... + + // If it is an engine Error with an error report then let's + // extract the report and build an xpcexception from that + const JSErrorReport* report; + if (nullptr != (report = JS_ErrorFromException(cx, obj))) { + JSAutoByteString toStringResult; + RootedString str(cx, ToString(cx, s)); + if (str) + toStringResult.encodeUtf8(cx, str); + return JSErrorToXPCException(toStringResult.ptr(), ifaceName, + methodName, report, exceptn); + } + + // XXX we should do a check against 'js_ErrorClass' here and + // do the right thing - even though it has no JSErrorReport, + // The fact that it is a JSError exceptions means we can extract + // particular info and our 'result' should reflect that. + + // otherwise we'll just try to convert it to a string + + JSString* str = ToString(cx, s); + if (!str) + return NS_ERROR_FAILURE; + + JSAutoByteString strBytes(cx, str); + if (!strBytes) + return NS_ERROR_FAILURE; + + return ConstructException(NS_ERROR_XPC_JS_THREW_JS_OBJECT, + strBytes.ptr(), ifaceName, methodName, + nullptr, exceptn, cx, s.address()); + } + } + + if (s.isUndefined() || s.isNull()) { + return ConstructException(NS_ERROR_XPC_JS_THREW_NULL, + nullptr, ifaceName, methodName, nullptr, + exceptn, cx, s.address()); + } + + if (s.isNumber()) { + // lets see if it looks like an nsresult + nsresult rv; + double number; + bool isResult = false; + + if (s.isInt32()) { + rv = (nsresult) s.toInt32(); + if (NS_FAILED(rv)) + isResult = true; + else + number = (double) s.toInt32(); + } else { + number = s.toDouble(); + if (number > 0.0 && + number < (double)0xffffffff && + 0.0 == fmod(number,1)) { + // Visual Studio 9 doesn't allow casting directly from a + // double to an enumeration type, contrary to 5.2.9(10) of + // C++11, so add an intermediate cast. + rv = (nsresult)(uint32_t) number; + if (NS_FAILED(rv)) + isResult = true; + } + } + + if (isResult) + return ConstructException(rv, nullptr, ifaceName, methodName, + nullptr, exceptn, cx, s.address()); + else { + // XXX all this nsISupportsDouble code seems a little redundant + // now that we're storing the Value in the exception... + nsCOMPtr data; + nsCOMPtr cm; + if (NS_FAILED(NS_GetComponentManager(getter_AddRefs(cm))) || !cm || + NS_FAILED(cm->CreateInstanceByContractID(NS_SUPPORTS_DOUBLE_CONTRACTID, + nullptr, + NS_GET_IID(nsISupportsDouble), + getter_AddRefs(data)))) + return NS_ERROR_FAILURE; + data->SetData(number); + rv = ConstructException(NS_ERROR_XPC_JS_THREW_NUMBER, nullptr, + ifaceName, methodName, data, exceptn, cx, s.address()); + return rv; + } + } + + // otherwise we'll just try to convert it to a string + // Note: e.g., bools get converted to JSStrings by this code. + + JSString* str = ToString(cx, s); + if (str) { + JSAutoByteString strBytes(cx, str); + if (!!strBytes) { + return ConstructException(NS_ERROR_XPC_JS_THREW_STRING, + strBytes.ptr(), ifaceName, methodName, + nullptr, exceptn, cx, s.address()); + } + } + return NS_ERROR_FAILURE; +} + +/***************************************************************************/ + +// array fun... + +#ifdef POPULATE +#undef POPULATE +#endif + +// static +bool +XPCConvert::NativeArray2JS(MutableHandleValue d, const void** s, + const nsXPTType& type, const nsID* iid, + uint32_t count, nsresult* pErr) +{ + NS_PRECONDITION(s, "bad param"); + + AutoJSContext cx; + + // XXX add support for putting chars in a string rather than an array + + // XXX add support to indicate *which* array element was not convertable + + RootedObject array(cx, JS_NewArrayObject(cx, count)); + if (!array) + return false; + + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + + uint32_t i; + RootedValue current(cx, JS::NullValue()); + +#define POPULATE(_t) \ + PR_BEGIN_MACRO \ + for (i = 0; i < count; i++) { \ + if (!NativeData2JS(¤t, ((_t*)*s)+i, type, iid, pErr) || \ + !JS_DefineElement(cx, array, i, current, JSPROP_ENUMERATE)) \ + return false; \ + } \ + PR_END_MACRO + + // XXX check IsPtr - esp. to handle array of nsID (as opposed to nsID*) + + switch (type.TagPart()) { + case nsXPTType::T_I8 : POPULATE(int8_t); break; + case nsXPTType::T_I16 : POPULATE(int16_t); break; + case nsXPTType::T_I32 : POPULATE(int32_t); break; + case nsXPTType::T_I64 : POPULATE(int64_t); break; + case nsXPTType::T_U8 : POPULATE(uint8_t); break; + case nsXPTType::T_U16 : POPULATE(uint16_t); break; + case nsXPTType::T_U32 : POPULATE(uint32_t); break; + case nsXPTType::T_U64 : POPULATE(uint64_t); break; + case nsXPTType::T_FLOAT : POPULATE(float); break; + case nsXPTType::T_DOUBLE : POPULATE(double); break; + case nsXPTType::T_BOOL : POPULATE(bool); break; + case nsXPTType::T_CHAR : POPULATE(char); break; + case nsXPTType::T_WCHAR : POPULATE(char16_t); break; + case nsXPTType::T_VOID : NS_ERROR("bad type"); return false; + case nsXPTType::T_IID : POPULATE(nsID*); break; + case nsXPTType::T_DOMSTRING : NS_ERROR("bad type"); return false; + case nsXPTType::T_CHAR_STR : POPULATE(char*); break; + case nsXPTType::T_WCHAR_STR : POPULATE(char16_t*); break; + case nsXPTType::T_INTERFACE : POPULATE(nsISupports*); break; + case nsXPTType::T_INTERFACE_IS : POPULATE(nsISupports*); break; + case nsXPTType::T_UTF8STRING : NS_ERROR("bad type"); return false; + case nsXPTType::T_CSTRING : NS_ERROR("bad type"); return false; + case nsXPTType::T_ASTRING : NS_ERROR("bad type"); return false; + default : NS_ERROR("bad type"); return false; + } + + if (pErr) + *pErr = NS_OK; + d.setObject(*array); + return true; + +#undef POPULATE +} + + + +// Check that the tag part of the type matches the type +// of the array. If the check succeeds, check that the size +// of the output does not exceed UINT32_MAX bytes. Allocate +// the memory and copy the elements by memcpy. +static bool +CheckTargetAndPopulate(const nsXPTType& type, + uint8_t requiredType, + size_t typeSize, + uint32_t count, + JSObject* tArr, + void** output, + nsresult* pErr) +{ + // Check that the element type expected by the interface matches + // the type of the elements in the typed array exactly, including + // signedness. + if (type.TagPart() != requiredType) { + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + return false; + } + + // Calulate the maximum number of elements that can fit in + // UINT32_MAX bytes. + size_t max = UINT32_MAX / typeSize; + + // This could overflow on 32-bit systems so check max first. + size_t byteSize = count * typeSize; + if (count > max || !(*output = moz_xmalloc(byteSize))) { + if (pErr) + *pErr = NS_ERROR_OUT_OF_MEMORY; + + return false; + } + + JS::AutoCheckCannotGC nogc; + bool isShared; + void* buf = JS_GetArrayBufferViewData(tArr, &isShared, nogc); + + // Require opting in to shared memory - a future project. + if (isShared) { + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + return false; + } + + memcpy(*output, buf, byteSize); + return true; +} + +// Fast conversion of typed arrays to native using memcpy. +// No float or double canonicalization is done. Called by +// JSarray2Native whenever a TypedArray is met. ArrayBuffers +// are not accepted; create a properly typed array view on them +// first. The element type of array must match the XPCOM +// type in size, type and signedness exactly. As an exception, +// Uint8ClampedArray is allowed for arrays of uint8_t. DataViews +// are not supported. + +// static +bool +XPCConvert::JSTypedArray2Native(void** d, + JSObject* jsArray, + uint32_t count, + const nsXPTType& type, + nsresult* pErr) +{ + MOZ_ASSERT(jsArray, "bad param"); + MOZ_ASSERT(d, "bad param"); + MOZ_ASSERT(JS_IsTypedArrayObject(jsArray), "not a typed array"); + + // Check the actual length of the input array against the + // given size_is. + uint32_t len = JS_GetTypedArrayLength(jsArray); + if (len < count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; + + return false; + } + + void* output = nullptr; + + switch (JS_GetArrayBufferViewType(jsArray)) { + case js::Scalar::Int8: + if (!CheckTargetAndPopulate(nsXPTType::T_I8, type, + sizeof(int8_t), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Uint8: + case js::Scalar::Uint8Clamped: + if (!CheckTargetAndPopulate(nsXPTType::T_U8, type, + sizeof(uint8_t), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Int16: + if (!CheckTargetAndPopulate(nsXPTType::T_I16, type, + sizeof(int16_t), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Uint16: + if (!CheckTargetAndPopulate(nsXPTType::T_U16, type, + sizeof(uint16_t), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Int32: + if (!CheckTargetAndPopulate(nsXPTType::T_I32, type, + sizeof(int32_t), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Uint32: + if (!CheckTargetAndPopulate(nsXPTType::T_U32, type, + sizeof(uint32_t), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Float32: + if (!CheckTargetAndPopulate(nsXPTType::T_FLOAT, type, + sizeof(float), count, + jsArray, &output, pErr)) { + return false; + } + break; + + case js::Scalar::Float64: + if (!CheckTargetAndPopulate(nsXPTType::T_DOUBLE, type, + sizeof(double), count, + jsArray, &output, pErr)) { + return false; + } + break; + + // Yet another array type was defined? It is not supported yet... + default: + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + + return false; + } + + *d = output; + if (pErr) + *pErr = NS_OK; + + return true; +} + +// static +bool +XPCConvert::JSArray2Native(void** d, HandleValue s, + uint32_t count, const nsXPTType& type, + const nsID* iid, nsresult* pErr) +{ + MOZ_ASSERT(d, "bad param"); + + AutoJSContext cx; + + // XXX add support for getting chars from strings + + // XXX add support to indicate *which* array element was not convertable + + if (s.isNullOrUndefined()) { + if (0 != count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; + return false; + } + + *d = nullptr; + return true; + } + + if (!s.isObject()) { + if (pErr) + *pErr = NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY; + return false; + } + + RootedObject jsarray(cx, &s.toObject()); + + // If this is a typed array, then try a fast conversion with memcpy. + if (JS_IsTypedArrayObject(jsarray)) { + return JSTypedArray2Native(d, jsarray, count, type, pErr); + } + + bool isArray; + if (!JS_IsArrayObject(cx, jsarray, &isArray) || !isArray) { + if (pErr) + *pErr = NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY; + return false; + } + + uint32_t len; + if (!JS_GetArrayLength(cx, jsarray, &len) || len < count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY; + return false; + } + + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_JS; + +#define POPULATE(_mode, _t) \ + PR_BEGIN_MACRO \ + cleanupMode = _mode; \ + size_t max = UINT32_MAX / sizeof(_t); \ + if (count > max || \ + nullptr == (array = moz_xmalloc(count * sizeof(_t)))) { \ + if (pErr) \ + *pErr = NS_ERROR_OUT_OF_MEMORY; \ + goto failure; \ + } \ + for (initedCount = 0; initedCount < count; initedCount++) { \ + if (!JS_GetElement(cx, jsarray, initedCount, ¤t) || \ + !JSData2Native(((_t*)array)+initedCount, current, type, \ + iid, pErr)) \ + goto failure; \ + } \ + PR_END_MACRO + + // No Action, FRee memory, RElease object + enum CleanupMode {na, fr, re}; + + CleanupMode cleanupMode; + + void* array = nullptr; + uint32_t initedCount; + RootedValue current(cx); + + // XXX check IsPtr - esp. to handle array of nsID (as opposed to nsID*) + // XXX make extra space at end of char* and wchar* and null termintate + + switch (type.TagPart()) { + case nsXPTType::T_I8 : POPULATE(na, int8_t); break; + case nsXPTType::T_I16 : POPULATE(na, int16_t); break; + case nsXPTType::T_I32 : POPULATE(na, int32_t); break; + case nsXPTType::T_I64 : POPULATE(na, int64_t); break; + case nsXPTType::T_U8 : POPULATE(na, uint8_t); break; + case nsXPTType::T_U16 : POPULATE(na, uint16_t); break; + case nsXPTType::T_U32 : POPULATE(na, uint32_t); break; + case nsXPTType::T_U64 : POPULATE(na, uint64_t); break; + case nsXPTType::T_FLOAT : POPULATE(na, float); break; + case nsXPTType::T_DOUBLE : POPULATE(na, double); break; + case nsXPTType::T_BOOL : POPULATE(na, bool); break; + case nsXPTType::T_CHAR : POPULATE(na, char); break; + case nsXPTType::T_WCHAR : POPULATE(na, char16_t); break; + case nsXPTType::T_VOID : NS_ERROR("bad type"); goto failure; + case nsXPTType::T_IID : POPULATE(fr, nsID*); break; + case nsXPTType::T_DOMSTRING : NS_ERROR("bad type"); goto failure; + case nsXPTType::T_CHAR_STR : POPULATE(fr, char*); break; + case nsXPTType::T_WCHAR_STR : POPULATE(fr, char16_t*); break; + case nsXPTType::T_INTERFACE : POPULATE(re, nsISupports*); break; + case nsXPTType::T_INTERFACE_IS : POPULATE(re, nsISupports*); break; + case nsXPTType::T_UTF8STRING : NS_ERROR("bad type"); goto failure; + case nsXPTType::T_CSTRING : NS_ERROR("bad type"); goto failure; + case nsXPTType::T_ASTRING : NS_ERROR("bad type"); goto failure; + default : NS_ERROR("bad type"); goto failure; + } + + *d = array; + if (pErr) + *pErr = NS_OK; + return true; + +failure: + // we may need to cleanup the partially filled array of converted stuff + if (array) { + if (cleanupMode == re) { + nsISupports** a = (nsISupports**) array; + for (uint32_t i = 0; i < initedCount; i++) { + nsISupports* p = a[i]; + NS_IF_RELEASE(p); + } + } else if (cleanupMode == fr) { + void** a = (void**) array; + for (uint32_t i = 0; i < initedCount; i++) { + void* p = a[i]; + if (p) free(p); + } + } + free(array); + } + + return false; + +#undef POPULATE +} + +// static +bool +XPCConvert::NativeStringWithSize2JS(MutableHandleValue d, const void* s, + const nsXPTType& type, + uint32_t count, + nsresult* pErr) +{ + NS_PRECONDITION(s, "bad param"); + + AutoJSContext cx; + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + + switch (type.TagPart()) { + case nsXPTType::T_PSTRING_SIZE_IS: + { + char* p = *((char**)s); + if (!p) + break; + JSString* str; + if (!(str = JS_NewStringCopyN(cx, p, count))) + return false; + d.setString(str); + break; + } + case nsXPTType::T_PWSTRING_SIZE_IS: + { + char16_t* p = *((char16_t**)s); + if (!p) + break; + JSString* str; + if (!(str = JS_NewUCStringCopyN(cx, p, count))) + return false; + d.setString(str); + break; + } + default: + XPC_LOG_ERROR(("XPCConvert::NativeStringWithSize2JS : unsupported type")); + return false; + } + return true; +} + +// static +bool +XPCConvert::JSStringWithSize2Native(void* d, HandleValue s, + uint32_t count, const nsXPTType& type, + nsresult* pErr) +{ + NS_PRECONDITION(!s.isNull(), "bad param"); + NS_PRECONDITION(d, "bad param"); + + AutoJSContext cx; + uint32_t len; + + if (pErr) + *pErr = NS_ERROR_XPC_BAD_CONVERT_NATIVE; + + switch (type.TagPart()) { + case nsXPTType::T_PSTRING_SIZE_IS: + { + if (s.isUndefined() || s.isNull()) { + if (0 != count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + return false; + } + if (0 != count) { + len = (count + 1) * sizeof(char); + if (!(*((void**)d) = moz_xmalloc(len))) + return false; + return true; + } + // else ... + + *((char**)d) = nullptr; + return true; + } + + JSString* str = ToString(cx, s); + if (!str) { + return false; + } + + size_t length = JS_GetStringEncodingLength(cx, str); + if (length == size_t(-1)) { + return false; + } + if (length > count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + return false; + } + len = uint32_t(length); + + if (len < count) + len = count; + + uint32_t alloc_len = (len + 1) * sizeof(char); + char* buffer = static_cast(moz_xmalloc(alloc_len)); + if (!buffer) { + return false; + } + JS_EncodeStringToBuffer(cx, str, buffer, len); + buffer[len] = '\0'; + *((char**)d) = buffer; + + return true; + } + + case nsXPTType::T_PWSTRING_SIZE_IS: + { + JSString* str; + + if (s.isUndefined() || s.isNull()) { + if (0 != count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + return false; + } + + if (0 != count) { + len = (count + 1) * sizeof(char16_t); + if (!(*((void**)d) = moz_xmalloc(len))) + return false; + return true; + } + + // else ... + *((const char16_t**)d) = nullptr; + return true; + } + + if (!(str = ToString(cx, s))) { + return false; + } + + len = JS_GetStringLength(str); + if (len > count) { + if (pErr) + *pErr = NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING; + return false; + } + + len = count; + + uint32_t alloc_len = (len + 1) * sizeof(char16_t); + if (!(*((void**)d) = moz_xmalloc(alloc_len))) { + // XXX should report error + return false; + } + mozilla::Range destChars(*((char16_t**)d), len + 1); + if (!JS_CopyStringChars(cx, destChars, str)) + return false; + destChars[count] = 0; + + return true; + } + default: + XPC_LOG_ERROR(("XPCConvert::JSStringWithSize2Native : unsupported type")); + return false; + } +} diff --git a/js/xpconnect/src/XPCDebug.cpp b/js/xpconnect/src/XPCDebug.cpp new file mode 100644 index 000000000..f8500ba10 --- /dev/null +++ b/js/xpconnect/src/XPCDebug.cpp @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "jsprf.h" +#include "nsThreadUtils.h" +#include "nsContentUtils.h" + +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +#include +#endif + +static void DebugDump(const char* fmt, ...) +{ + char buffer[2048]; + va_list ap; + va_start(ap, fmt); +#ifdef XPWIN + _vsnprintf(buffer, sizeof(buffer), fmt, ap); + buffer[sizeof(buffer)-1] = '\0'; +#else + VsprintfLiteral(buffer, fmt, ap); +#endif + va_end(ap); +#ifdef XP_WIN + if (IsDebuggerPresent()) { + OutputDebugStringA(buffer); + } +#endif + printf("%s", buffer); +} + +bool +xpc_DumpJSStack(bool showArgs, bool showLocals, bool showThisProps) +{ + JSContext* cx = nsContentUtils::GetCurrentJSContextForThread(); + if (!cx) { + printf("there is no JSContext on the stack!\n"); + } else if (char* buf = xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps)) { + DebugDump("%s\n", buf); + JS_smprintf_free(buf); + } + return true; +} + +char* +xpc_PrintJSStack(JSContext* cx, bool showArgs, bool showLocals, + bool showThisProps) +{ + JS::AutoSaveExceptionState state(cx); + + char* buf = JS::FormatStackDump(cx, nullptr, showArgs, showLocals, showThisProps); + if (!buf) + DebugDump("%s", "Failed to format JavaScript stack for dump\n"); + + state.restore(); + return buf; +} diff --git a/js/xpconnect/src/XPCException.cpp b/js/xpconnect/src/XPCException.cpp new file mode 100644 index 000000000..575cc4aa3 --- /dev/null +++ b/js/xpconnect/src/XPCException.cpp @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* An implementaion of nsIException. */ + +#include "xpcprivate.h" +#include "nsError.h" + +/***************************************************************************/ +/* Quick and dirty mapping of well known result codes to strings. We only +* call this when building an exception object, so iterating the short array +* is not too bad. +* +* It sure would be nice to have exceptions declared in idl and available +* in some more global way at runtime. +*/ + +static const struct ResultMap +{nsresult rv; const char* name; const char* format;} map[] = { +#define XPC_MSG_DEF(val, format) \ + {(val), #val, format}, +#include "xpc.msg" +#undef XPC_MSG_DEF + {NS_OK,0,0} // sentinel to mark end of array +}; + +#define RESULT_COUNT ((sizeof(map) / sizeof(map[0]))-1) + +// static +bool +nsXPCException::NameAndFormatForNSResult(nsresult rv, + const char** name, + const char** format) +{ + + for (const ResultMap* p = map; p->name; p++) { + if (rv == p->rv) { + if (name) *name = p->name; + if (format) *format = p->format; + return true; + } + } + return false; +} + +// static +const void* +nsXPCException::IterateNSResults(nsresult* rv, + const char** name, + const char** format, + const void** iterp) +{ + const ResultMap* p = (const ResultMap*) *iterp; + if (!p) + p = map; + else + p++; + if (!p->name) + p = nullptr; + else { + if (rv) + *rv = p->rv; + if (name) + *name = p->name; + if (format) + *format = p->format; + } + *iterp = p; + return p; +} + +// static +uint32_t +nsXPCException::GetNSResultCount() +{ + return RESULT_COUNT; +} diff --git a/js/xpconnect/src/XPCForwards.h b/js/xpconnect/src/XPCForwards.h new file mode 100644 index 000000000..c1259bb3a --- /dev/null +++ b/js/xpconnect/src/XPCForwards.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Private forward declarations. */ + +#ifndef xpcforwards_h___ +#define xpcforwards_h___ + +// forward declarations of interally used classes... + +class nsXPConnect; +class XPCJSContext; +class XPCContext; +class XPCCallContext; + +class XPCJSThrower; + +class nsXPCWrappedJS; +class nsXPCWrappedJSClass; + +class XPCNativeMember; +class XPCNativeInterface; +class XPCNativeSet; + +class XPCWrappedNative; +class XPCWrappedNativeProto; +class XPCWrappedNativeTearOff; +class XPCNativeScriptableInfo; +class XPCNativeScriptableCreateInfo; + +class XPCTraceableVariant; +class XPCJSObjectHolder; + +class JSObject2WrappedJSMap; +class Native2WrappedNativeMap; +class IID2WrappedJSClassMap; +class IID2NativeInterfaceMap; +class ClassInfo2NativeSetMap; +class ClassInfo2WrappedNativeProtoMap; +class NativeSetMap; +class IID2ThisTranslatorMap; +class XPCWrappedNativeProtoMap; +class JSObject2JSObjectMap; + +class nsXPCComponents; +class nsXPCComponents_Interfaces; +class nsXPCComponents_InterfacesByID; +class nsXPCComponents_Classes; +class nsXPCComponents_ClassesByID; +class nsXPCComponents_Results; +class nsXPCComponents_ID; +class nsXPCComponents_Exception; +class nsXPCComponents_Constructor; +class nsXPCComponents_Utils; +class nsXPCConstructor; + +class AutoMarkingPtr; + +class xpcProperty; + +#endif /* xpcforwards_h___ */ diff --git a/js/xpconnect/src/XPCInlines.h b/js/xpconnect/src/XPCInlines.h new file mode 100644 index 000000000..20c63c972 --- /dev/null +++ b/js/xpconnect/src/XPCInlines.h @@ -0,0 +1,545 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* private inline methods (#include'd by xpcprivate.h). */ + +#ifndef xpcinlines_h___ +#define xpcinlines_h___ + +#include + +/***************************************************************************/ + +inline void +XPCJSContext::AddVariantRoot(XPCTraceableVariant* variant) +{ + variant->AddToRootSet(&mVariantRoots); +} + +inline void +XPCJSContext::AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS) +{ + wrappedJS->AddToRootSet(&mWrappedJSRoots); +} + +inline void +XPCJSContext::AddObjectHolderRoot(XPCJSObjectHolder* holder) +{ + holder->AddToRootSet(&mObjectHolderRoots); +} + +/***************************************************************************/ + +inline bool +XPCCallContext::IsValid() const +{ + return mState != INIT_FAILED; +} + +inline XPCJSContext* +XPCCallContext::GetContext() const +{ + CHECK_STATE(HAVE_CONTEXT); + return mXPCJSContext; +} + +inline JSContext* +XPCCallContext::GetJSContext() const +{ + CHECK_STATE(HAVE_CONTEXT); + return mJSContext; +} + +inline XPCCallContext* +XPCCallContext::GetPrevCallContext() const +{ + CHECK_STATE(HAVE_CONTEXT); + return mPrevCallContext; +} + +inline nsISupports* +XPCCallContext::GetIdentityObject() const +{ + CHECK_STATE(HAVE_OBJECT); + if (mWrapper) + return mWrapper->GetIdentityObject(); + return nullptr; +} + +inline XPCWrappedNative* +XPCCallContext::GetWrapper() const +{ + if (mState == INIT_FAILED) + return nullptr; + + CHECK_STATE(HAVE_OBJECT); + return mWrapper; +} + +inline XPCWrappedNativeProto* +XPCCallContext::GetProto() const +{ + CHECK_STATE(HAVE_OBJECT); + return mWrapper ? mWrapper->GetProto() : nullptr; +} + +inline bool +XPCCallContext::CanGetTearOff() const +{ + return mState >= HAVE_OBJECT; +} + +inline XPCWrappedNativeTearOff* +XPCCallContext::GetTearOff() const +{ + CHECK_STATE(HAVE_OBJECT); + return mTearOff; +} + +inline XPCNativeScriptableInfo* +XPCCallContext::GetScriptableInfo() const +{ + CHECK_STATE(HAVE_OBJECT); + return mScriptableInfo; +} + +inline bool +XPCCallContext::CanGetSet() const +{ + return mState >= HAVE_NAME; +} + +inline XPCNativeSet* +XPCCallContext::GetSet() const +{ + CHECK_STATE(HAVE_NAME); + return mSet; +} + +inline XPCNativeInterface* +XPCCallContext::GetInterface() const +{ + CHECK_STATE(HAVE_NAME); + return mInterface; +} + +inline XPCNativeMember* +XPCCallContext::GetMember() const +{ + CHECK_STATE(HAVE_NAME); + return mMember; +} + +inline bool +XPCCallContext::HasInterfaceAndMember() const +{ + return mState >= HAVE_NAME && mInterface && mMember; +} + +inline jsid +XPCCallContext::GetName() const +{ + CHECK_STATE(HAVE_NAME); + return mName; +} + +inline bool +XPCCallContext::GetStaticMemberIsLocal() const +{ + CHECK_STATE(HAVE_NAME); + return mStaticMemberIsLocal; +} + +inline unsigned +XPCCallContext::GetArgc() const +{ + CHECK_STATE(READY_TO_CALL); + return mArgc; +} + +inline JS::Value* +XPCCallContext::GetArgv() const +{ + CHECK_STATE(READY_TO_CALL); + return mArgv; +} + +inline JS::Value* +XPCCallContext::GetRetVal() const +{ + CHECK_STATE(READY_TO_CALL); + return mRetVal; +} + +inline void +XPCCallContext::SetRetVal(const JS::Value& val) +{ + CHECK_STATE(HAVE_ARGS); + if (mRetVal) + *mRetVal = val; +} + +inline jsid +XPCCallContext::GetResolveName() const +{ + CHECK_STATE(HAVE_CONTEXT); + return XPCJSContext::Get()->GetResolveName(); +} + +inline jsid +XPCCallContext::SetResolveName(JS::HandleId name) +{ + CHECK_STATE(HAVE_CONTEXT); + return XPCJSContext::Get()->SetResolveName(name); +} + +inline XPCWrappedNative* +XPCCallContext::GetResolvingWrapper() const +{ + CHECK_STATE(HAVE_OBJECT); + return XPCJSContext::Get()->GetResolvingWrapper(); +} + +inline XPCWrappedNative* +XPCCallContext::SetResolvingWrapper(XPCWrappedNative* w) +{ + CHECK_STATE(HAVE_OBJECT); + return XPCJSContext::Get()->SetResolvingWrapper(w); +} + +inline uint16_t +XPCCallContext::GetMethodIndex() const +{ + CHECK_STATE(HAVE_OBJECT); + return mMethodIndex; +} + +inline void +XPCCallContext::SetMethodIndex(uint16_t index) +{ + CHECK_STATE(HAVE_OBJECT); + mMethodIndex = index; +} + +/***************************************************************************/ +inline XPCNativeInterface* +XPCNativeMember::GetInterface() const +{ + XPCNativeMember* arrayStart = + const_cast(this - mIndexInInterface); + size_t arrayStartOffset = XPCNativeInterface::OffsetOfMembers(); + char* xpcNativeInterfaceStart = + reinterpret_cast(arrayStart) - arrayStartOffset; + return reinterpret_cast(xpcNativeInterfaceStart); +} + +/***************************************************************************/ + +inline const nsIID* +XPCNativeInterface::GetIID() const +{ + const nsIID* iid; + return NS_SUCCEEDED(mInfo->GetIIDShared(&iid)) ? iid : nullptr; +} + +inline const char* +XPCNativeInterface::GetNameString() const +{ + const char* name; + return NS_SUCCEEDED(mInfo->GetNameShared(&name)) ? name : nullptr; +} + +inline XPCNativeMember* +XPCNativeInterface::FindMember(jsid name) const +{ + const XPCNativeMember* member = mMembers; + for (int i = (int) mMemberCount; i > 0; i--, member++) + if (member->GetName() == name) + return const_cast(member); + return nullptr; +} + +inline bool +XPCNativeInterface::HasAncestor(const nsIID* iid) const +{ + bool found = false; + mInfo->HasAncestor(iid, &found); + return found; +} + +/* static */ +inline size_t +XPCNativeInterface::OffsetOfMembers() +{ + return offsetof(XPCNativeInterface, mMembers); +} + +/***************************************************************************/ + +inline XPCNativeSetKey::XPCNativeSetKey(XPCNativeSet* baseSet, + XPCNativeInterface* addition) + : mBaseSet(baseSet) + , mAddition(addition) +{ + MOZ_ASSERT(mBaseSet); + MOZ_ASSERT(mAddition); + MOZ_ASSERT(!mBaseSet->HasInterface(mAddition)); +} + +/***************************************************************************/ + +inline bool +XPCNativeSet::FindMember(jsid name, XPCNativeMember** pMember, + uint16_t* pInterfaceIndex) const +{ + XPCNativeInterface* const * iface; + int count = (int) mInterfaceCount; + int i; + + // look for interface names first + + for (i = 0, iface = mInterfaces; i < count; i++, iface++) { + if (name == (*iface)->GetName()) { + if (pMember) + *pMember = nullptr; + if (pInterfaceIndex) + *pInterfaceIndex = (uint16_t) i; + return true; + } + } + + // look for method names + for (i = 0, iface = mInterfaces; i < count; i++, iface++) { + XPCNativeMember* member = (*iface)->FindMember(name); + if (member) { + if (pMember) + *pMember = member; + if (pInterfaceIndex) + *pInterfaceIndex = (uint16_t) i; + return true; + } + } + return false; +} + +inline bool +XPCNativeSet::FindMember(jsid name, XPCNativeMember** pMember, + RefPtr* pInterface) const +{ + uint16_t index; + if (!FindMember(name, pMember, &index)) + return false; + *pInterface = mInterfaces[index]; + return true; +} + +inline bool +XPCNativeSet::FindMember(JS::HandleId name, + XPCNativeMember** pMember, + RefPtr* pInterface, + XPCNativeSet* protoSet, + bool* pIsLocal) const +{ + XPCNativeMember* Member; + RefPtr Interface; + XPCNativeMember* protoMember; + + if (!FindMember(name, &Member, &Interface)) + return false; + + *pMember = Member; + + *pIsLocal = + !Member || + !protoSet || + (protoSet != this && + !protoSet->MatchesSetUpToInterface(this, Interface) && + (!protoSet->FindMember(name, &protoMember, (uint16_t*)nullptr) || + protoMember != Member)); + + *pInterface = Interface.forget(); + + return true; +} + +inline XPCNativeInterface* +XPCNativeSet::FindNamedInterface(jsid name) const +{ + XPCNativeInterface* const * pp = mInterfaces; + + for (int i = (int) mInterfaceCount; i > 0; i--, pp++) { + XPCNativeInterface* iface = *pp; + + if (name == iface->GetName()) + return iface; + } + return nullptr; +} + +inline XPCNativeInterface* +XPCNativeSet::FindInterfaceWithIID(const nsIID& iid) const +{ + XPCNativeInterface* const * pp = mInterfaces; + + for (int i = (int) mInterfaceCount; i > 0; i--, pp++) { + XPCNativeInterface* iface = *pp; + + if (iface->GetIID()->Equals(iid)) + return iface; + } + return nullptr; +} + +inline bool +XPCNativeSet::HasInterface(XPCNativeInterface* aInterface) const +{ + XPCNativeInterface* const * pp = mInterfaces; + + for (int i = (int) mInterfaceCount; i > 0; i--, pp++) { + if (aInterface == *pp) + return true; + } + return false; +} + +inline bool +XPCNativeSet::HasInterfaceWithAncestor(XPCNativeInterface* aInterface) const +{ + return HasInterfaceWithAncestor(aInterface->GetIID()); +} + +inline bool +XPCNativeSet::HasInterfaceWithAncestor(const nsIID* iid) const +{ + // We can safely skip the first interface which is *always* nsISupports. + XPCNativeInterface* const * pp = mInterfaces+1; + for (int i = (int) mInterfaceCount; i > 1; i--, pp++) + if ((*pp)->HasAncestor(iid)) + return true; + + // This is rare, so check last. + if (iid == &NS_GET_IID(nsISupports)) + return true; + + return false; +} + +inline bool +XPCNativeSet::MatchesSetUpToInterface(const XPCNativeSet* other, + XPCNativeInterface* iface) const +{ + int count = std::min(int(mInterfaceCount), int(other->mInterfaceCount)); + + XPCNativeInterface* const * pp1 = mInterfaces; + XPCNativeInterface* const * pp2 = other->mInterfaces; + + for (int i = (int) count; i > 0; i--, pp1++, pp2++) { + XPCNativeInterface* cur = (*pp1); + if (cur != (*pp2)) + return false; + if (cur == iface) + return true; + } + return false; +} + +/***************************************************************************/ + +inline +JSObject* XPCWrappedNativeTearOff::GetJSObjectPreserveColor() const +{ + return mJSObject.unbarrieredGetPtr(); +} + +inline +JSObject* XPCWrappedNativeTearOff::GetJSObject() +{ + return mJSObject; +} + +inline +void XPCWrappedNativeTearOff::SetJSObject(JSObject* JSObj) +{ + MOZ_ASSERT(!IsMarked()); + mJSObject = JSObj; +} + +inline +void XPCWrappedNativeTearOff::JSObjectMoved(JSObject* obj, const JSObject* old) +{ + MOZ_ASSERT(!IsMarked()); + MOZ_ASSERT(mJSObject.unbarrieredGetPtr() == old); + mJSObject = obj; +} + +inline +XPCWrappedNativeTearOff::~XPCWrappedNativeTearOff() +{ + MOZ_COUNT_DTOR(XPCWrappedNativeTearOff); + MOZ_ASSERT(!(GetInterface() || GetNative() || GetJSObjectPreserveColor()), + "tearoff not empty in dtor"); +} + +/***************************************************************************/ + +inline bool +XPCWrappedNative::HasInterfaceNoQI(const nsIID& iid) +{ + return nullptr != GetSet()->FindInterfaceWithIID(iid); +} + +inline void +XPCWrappedNative::SweepTearOffs() +{ + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) { + bool marked = to->IsMarked(); + to->Unmark(); + if (marked) + continue; + + // If this tearoff does not have a live dedicated JSObject, + // then let's recycle it. + if (!to->GetJSObjectPreserveColor()) { + to->SetNative(nullptr); + to->SetInterface(nullptr); + } + } +} + +/***************************************************************************/ + +inline bool +xpc_ForcePropertyResolve(JSContext* cx, JS::HandleObject obj, jsid idArg) +{ + JS::RootedId id(cx, idArg); + bool dummy; + return JS_HasPropertyById(cx, obj, id, &dummy); +} + +inline jsid +GetJSIDByIndex(JSContext* cx, unsigned index) +{ + XPCJSContext* xpcx = nsXPConnect::XPConnect()->GetContext(); + return xpcx->GetStringID(index); +} + +inline +bool ThrowBadParam(nsresult rv, unsigned paramNum, XPCCallContext& ccx) +{ + XPCThrower::ThrowBadParam(rv, paramNum, ccx); + return false; +} + +inline +void ThrowBadResult(nsresult result, XPCCallContext& ccx) +{ + XPCThrower::ThrowBadResult(NS_ERROR_XPC_NATIVE_RETURNED_FAILURE, + result, ccx); +} + +/***************************************************************************/ + +#endif /* xpcinlines_h___ */ diff --git a/js/xpconnect/src/XPCJSContext.cpp b/js/xpconnect/src/XPCJSContext.cpp new file mode 100644 index 000000000..6981b525c --- /dev/null +++ b/js/xpconnect/src/XPCJSContext.cpp @@ -0,0 +1,3771 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Per JSContext object */ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" + +#include "xpcprivate.h" +#include "xpcpublic.h" +#include "XPCWrapper.h" +#include "XPCJSMemoryReporter.h" +#include "WrapperFactory.h" +#include "mozJSComponentLoader.h" +#include "nsAutoPtr.h" +#include "nsNetUtil.h" + +#include "nsIMemoryInfoDumper.h" +#include "nsIMemoryReporter.h" +#include "nsIObserverService.h" +#include "nsIDebug2.h" +#include "nsIDocShell.h" +#include "nsIRunnable.h" +#include "amIAddonManager.h" +#include "nsPIDOMWindow.h" +#include "nsPrintfCString.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Services.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "nsContentUtils.h" +#include "nsCCUncollectableMarker.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollector.h" +#include "nsScriptLoader.h" +#include "jsapi.h" +#include "jsprf.h" +#include "js/MemoryMetrics.h" +#include "mozilla/dom/GeneratedAtomList.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/Sprintf.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/Unused.h" +#include "AccessCheck.h" +#include "nsGlobalWindow.h" +#include "nsAboutProtocolUtils.h" + +#include "GeckoProfiler.h" +#include "nsIXULRuntime.h" +#include "nsJSPrincipals.h" + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#endif + +#if defined(MOZ_JEMALLOC4) +#include "mozmemory.h" +#endif + +#ifdef XP_WIN +#include +#endif + +using namespace mozilla; +using namespace xpc; +using namespace JS; +using mozilla::dom::PerThreadAtomCache; +using mozilla::dom::AutoEntryScript; + +/***************************************************************************/ + +const char* const XPCJSContext::mStrings[] = { + "constructor", // IDX_CONSTRUCTOR + "toString", // IDX_TO_STRING + "toSource", // IDX_TO_SOURCE + "lastResult", // IDX_LAST_RESULT + "returnCode", // IDX_RETURN_CODE + "value", // IDX_VALUE + "QueryInterface", // IDX_QUERY_INTERFACE + "Components", // IDX_COMPONENTS + "wrappedJSObject", // IDX_WRAPPED_JSOBJECT + "Object", // IDX_OBJECT + "Function", // IDX_FUNCTION + "prototype", // IDX_PROTOTYPE + "createInstance", // IDX_CREATE_INSTANCE + "item", // IDX_ITEM + "__proto__", // IDX_PROTO + "__iterator__", // IDX_ITERATOR + "__exposedProps__", // IDX_EXPOSEDPROPS + "eval", // IDX_EVAL + "controllers", // IDX_CONTROLLERS + "realFrameElement", // IDX_REALFRAMEELEMENT + "length", // IDX_LENGTH + "name", // IDX_NAME + "undefined", // IDX_UNDEFINED + "", // IDX_EMPTYSTRING + "fileName", // IDX_FILENAME + "lineNumber", // IDX_LINENUMBER + "columnNumber", // IDX_COLUMNNUMBER + "stack", // IDX_STACK + "message", // IDX_MESSAGE + "lastIndex" // IDX_LASTINDEX +}; + +/***************************************************************************/ + +static mozilla::Atomic sDiscardSystemSource(false); + +bool +xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; } + +#ifdef DEBUG +static mozilla::Atomic sExtraWarningsForSystemJS(false); +bool xpc::ExtraWarningsForSystemJS() { return sExtraWarningsForSystemJS; } +#else +bool xpc::ExtraWarningsForSystemJS() { return false; } +#endif + +static mozilla::Atomic sSharedMemoryEnabled(false); + +bool +xpc::SharedMemoryEnabled() { return sSharedMemoryEnabled; } + +// *Some* NativeSets are referenced from mClassInfo2NativeSetMap. +// *All* NativeSets are referenced from mNativeSetMap. +// So, in mClassInfo2NativeSetMap we just clear references to the unmarked. +// In mNativeSetMap we clear the references to the unmarked *and* delete them. + +class AsyncFreeSnowWhite : public Runnable +{ +public: + NS_IMETHOD Run() override + { + TimeStamp start = TimeStamp::Now(); + bool hadSnowWhiteObjects = nsCycleCollector_doDeferredDeletion(); + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING, + uint32_t((TimeStamp::Now() - start).ToMilliseconds())); + if (hadSnowWhiteObjects && !mContinuation) { + mContinuation = true; + if (NS_FAILED(NS_DispatchToCurrentThread(this))) { + mActive = false; + } + } else { +#if defined(MOZ_JEMALLOC4) + if (mPurge) { + /* Jemalloc purges dirty pages regularly during free() when the + * ratio of dirty pages compared to active pages is higher than + * 1 << lg_dirty_mult. A high ratio can have an impact on + * performance, so we use the default ratio of 8, but force a + * regular purge of all remaining dirty pages, after cycle + * collection. */ + Telemetry::AutoTimer timer; + jemalloc_free_dirty_pages(); + } +#endif + mActive = false; + } + return NS_OK; + } + + void Dispatch(bool aContinuation = false, bool aPurge = false) + { + if (mContinuation) { + mContinuation = aContinuation; + } + mPurge = aPurge; + if (!mActive && NS_SUCCEEDED(NS_DispatchToCurrentThread(this))) { + mActive = true; + } + } + + AsyncFreeSnowWhite() : mContinuation(false), mActive(false), mPurge(false) {} + +public: + bool mContinuation; + bool mActive; + bool mPurge; +}; + +namespace xpc { + +CompartmentPrivate::CompartmentPrivate(JSCompartment* c) + : wantXrays(false) + , allowWaivers(true) + , writeToGlobalPrototype(false) + , skipWriteToGlobalPrototype(false) + , isWebExtensionContentScript(false) + , waiveInterposition(false) + , allowCPOWs(false) + , universalXPConnectEnabled(false) + , forcePermissiveCOWs(false) + , scriptability(c) + , scope(nullptr) + , mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)) +{ + MOZ_COUNT_CTOR(xpc::CompartmentPrivate); + mozilla::PodArrayZero(wrapperDenialWarnings); +} + +CompartmentPrivate::~CompartmentPrivate() +{ + MOZ_COUNT_DTOR(xpc::CompartmentPrivate); + mWrappedJSMap->ShutdownMarker(); + delete mWrappedJSMap; +} + +static bool +TryParseLocationURICandidate(const nsACString& uristr, + CompartmentPrivate::LocationHint aLocationHint, + nsIURI** aURI) +{ + static NS_NAMED_LITERAL_CSTRING(kGRE, "resource://gre/"); + static NS_NAMED_LITERAL_CSTRING(kToolkit, "chrome://global/"); + static NS_NAMED_LITERAL_CSTRING(kBrowser, "chrome://browser/"); + + if (aLocationHint == CompartmentPrivate::LocationHintAddon) { + // Blacklist some known locations which are clearly not add-on related. + if (StringBeginsWith(uristr, kGRE) || + StringBeginsWith(uristr, kToolkit) || + StringBeginsWith(uristr, kBrowser)) + return false; + + // -- GROSS HACK ALERT -- + // The Yandex Elements 8.10.2 extension implements its own "xb://" URL + // scheme. If we call NS_NewURI() on an "xb://..." URL, we'll end up + // calling into the extension's own JS-implemented nsIProtocolHandler + // object, which we can't allow while we're iterating over the JS heap. + // So just skip any such URL. + // -- GROSS HACK ALERT -- + if (StringBeginsWith(uristr, NS_LITERAL_CSTRING("xb"))) + return false; + } + + nsCOMPtr uri; + if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), uristr))) + return false; + + nsAutoCString scheme; + if (NS_FAILED(uri->GetScheme(scheme))) + return false; + + // Cannot really map data: and blob:. + // Also, data: URIs are pretty memory hungry, which is kinda bad + // for memory reporter use. + if (scheme.EqualsLiteral("data") || scheme.EqualsLiteral("blob")) + return false; + + uri.forget(aURI); + return true; +} + +bool CompartmentPrivate::TryParseLocationURI(CompartmentPrivate::LocationHint aLocationHint, + nsIURI** aURI) +{ + if (!aURI) + return false; + + // Need to parse the URI. + if (location.IsEmpty()) + return false; + + // Handle Sandbox location strings. + // A sandbox string looks like this: + // (from: :) + // where is user-provided via Cu.Sandbox() + // and and is the stack frame location + // from where Cu.Sandbox was called. + // furthermore is "free form", often using a + // "uri -> uri -> ..." chain. The following code will and must handle this + // common case. + // It should be noted that other parts of the code may already rely on the + // "format" of these strings, such as the add-on SDK. + + static const nsDependentCString from("(from: "); + static const nsDependentCString arrow(" -> "); + static const size_t fromLength = from.Length(); + static const size_t arrowLength = arrow.Length(); + + // See: XPCComponents.cpp#AssembleSandboxMemoryReporterName + int32_t idx = location.Find(from); + if (idx < 0) + return TryParseLocationURICandidate(location, aLocationHint, aURI); + + + // When parsing we're looking for the right-most URI. This URI may be in + // , so we try this first. + if (TryParseLocationURICandidate(Substring(location, 0, idx), aLocationHint, + aURI)) + return true; + + // Not in so we need to inspect and + // the chain that is potentially contained within and grab the rightmost + // item that is actually a URI. + + // First, hack off the :) part as well + int32_t ridx = location.RFind(NS_LITERAL_CSTRING(":")); + nsAutoCString chain(Substring(location, idx + fromLength, + ridx - idx - fromLength)); + + // Loop over the "->" chain. This loop also works for non-chains, or more + // correctly chains with only one item. + for (;;) { + idx = chain.RFind(arrow); + if (idx < 0) { + // This is the last chain item. Try to parse what is left. + return TryParseLocationURICandidate(chain, aLocationHint, aURI); + } + + // Try to parse current chain item + if (TryParseLocationURICandidate(Substring(chain, idx + arrowLength), + aLocationHint, aURI)) + return true; + + // Current chain item couldn't be parsed. + // Strip current item and continue. + chain = Substring(chain, 0, idx); + } + + MOZ_CRASH("Chain parser loop does not terminate"); +} + +static bool +PrincipalImmuneToScriptPolicy(nsIPrincipal* aPrincipal) +{ + // System principal gets a free pass. + if (nsXPConnect::SecurityManager()->IsSystemPrincipal(aPrincipal)) + return true; + + // nsExpandedPrincipal gets a free pass. + nsCOMPtr ep = do_QueryInterface(aPrincipal); + if (ep) + return true; + + // Check whether our URI is an "about:" URI that allows scripts. If it is, + // we need to allow JS to run. + nsCOMPtr principalURI; + aPrincipal->GetURI(getter_AddRefs(principalURI)); + MOZ_ASSERT(principalURI); + bool isAbout; + nsresult rv = principalURI->SchemeIs("about", &isAbout); + if (NS_SUCCEEDED(rv) && isAbout) { + nsCOMPtr module; + rv = NS_GetAboutModule(principalURI, getter_AddRefs(module)); + if (NS_SUCCEEDED(rv)) { + uint32_t flags; + rv = module->GetURIFlags(principalURI, &flags); + if (NS_SUCCEEDED(rv) && + (flags & nsIAboutModule::ALLOW_SCRIPT)) { + return true; + } + } + } + + return false; +} + +Scriptability::Scriptability(JSCompartment* c) : mScriptBlocks(0) + , mDocShellAllowsScript(true) + , mScriptBlockedByPolicy(false) +{ + nsIPrincipal* prin = nsJSPrincipals::get(JS_GetCompartmentPrincipals(c)); + mImmuneToScriptPolicy = PrincipalImmuneToScriptPolicy(prin); + + // If we're not immune, we should have a real principal with a codebase URI. + // Check the URI against the new-style domain policy. + if (!mImmuneToScriptPolicy) { + nsCOMPtr codebase; + nsresult rv = prin->GetURI(getter_AddRefs(codebase)); + bool policyAllows; + if (NS_SUCCEEDED(rv) && codebase && + NS_SUCCEEDED(nsXPConnect::SecurityManager()->PolicyAllowsScript(codebase, &policyAllows))) + { + mScriptBlockedByPolicy = !policyAllows; + } else { + // Something went wrong - be safe and block script. + mScriptBlockedByPolicy = true; + } + } +} + +bool +Scriptability::Allowed() +{ + return mDocShellAllowsScript && !mScriptBlockedByPolicy && + mScriptBlocks == 0; +} + +bool +Scriptability::IsImmuneToScriptPolicy() +{ + return mImmuneToScriptPolicy; +} + +void +Scriptability::Block() +{ + ++mScriptBlocks; +} + +void +Scriptability::Unblock() +{ + MOZ_ASSERT(mScriptBlocks > 0); + --mScriptBlocks; +} + +void +Scriptability::SetDocShellAllowsScript(bool aAllowed) +{ + mDocShellAllowsScript = aAllowed || mImmuneToScriptPolicy; +} + +/* static */ +Scriptability& +Scriptability::Get(JSObject* aScope) +{ + return CompartmentPrivate::Get(aScope)->scriptability; +} + +bool +IsContentXBLScope(JSCompartment* compartment) +{ + // We always eagerly create compartment privates for XBL scopes. + CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); + if (!priv || !priv->scope) + return false; + return priv->scope->IsContentXBLScope(); +} + +bool +IsInContentXBLScope(JSObject* obj) +{ + return IsContentXBLScope(js::GetObjectCompartment(obj)); +} + +bool +IsInAddonScope(JSObject* obj) +{ + return ObjectScope(obj)->IsAddonScope(); +} + +bool +IsUniversalXPConnectEnabled(JSCompartment* compartment) +{ + CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); + if (!priv) + return false; + return priv->universalXPConnectEnabled; +} + +bool +IsUniversalXPConnectEnabled(JSContext* cx) +{ + JSCompartment* compartment = js::GetContextCompartment(cx); + if (!compartment) + return false; + return IsUniversalXPConnectEnabled(compartment); +} + +bool +EnableUniversalXPConnect(JSContext* cx) +{ + JSCompartment* compartment = js::GetContextCompartment(cx); + if (!compartment) + return true; + // Never set universalXPConnectEnabled on a chrome compartment - it confuses + // the security wrapping code. + if (AccessCheck::isChrome(compartment)) + return true; + CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); + if (!priv) + return true; + if (priv->universalXPConnectEnabled) + return true; + priv->universalXPConnectEnabled = true; + + // Recompute all the cross-compartment wrappers leaving the newly-privileged + // compartment. + bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment), + js::AllCompartments()); + NS_ENSURE_TRUE(ok, false); + + // The Components object normally isn't defined for unprivileged web content, + // but we define it when UniversalXPConnect is enabled to support legacy + // tests. + XPCWrappedNativeScope* scope = priv->scope; + if (!scope) + return true; + scope->ForcePrivilegedComponents(); + return scope->AttachComponentsObject(cx); +} + +JSObject* +UnprivilegedJunkScope() +{ + return XPCJSContext::Get()->UnprivilegedJunkScope(); +} + +JSObject* +PrivilegedJunkScope() +{ + return XPCJSContext::Get()->PrivilegedJunkScope(); +} + +JSObject* +CompilationScope() +{ + return XPCJSContext::Get()->CompilationScope(); +} + +nsGlobalWindow* +WindowOrNull(JSObject* aObj) +{ + MOZ_ASSERT(aObj); + MOZ_ASSERT(!js::IsWrapper(aObj)); + + nsGlobalWindow* win = nullptr; + UNWRAP_NON_WRAPPER_OBJECT(Window, aObj, win); + return win; +} + +nsGlobalWindow* +WindowGlobalOrNull(JSObject* aObj) +{ + MOZ_ASSERT(aObj); + JSObject* glob = js::GetGlobalForObjectCrossCompartment(aObj); + + return WindowOrNull(glob); +} + +nsGlobalWindow* +AddonWindowOrNull(JSObject* aObj) +{ + if (!IsInAddonScope(aObj)) + return nullptr; + + JSObject* global = js::GetGlobalForObjectCrossCompartment(aObj); + JSObject* proto = js::GetPrototypeNoProxy(global); + + // Addons could theoretically change the prototype of the addon scope, but + // we pretty much just want to crash if that happens so that we find out + // about it and get them to change their code. + MOZ_RELEASE_ASSERT(js::IsCrossCompartmentWrapper(proto) || + xpc::IsSandboxPrototypeProxy(proto)); + JSObject* mainGlobal = js::UncheckedUnwrap(proto, /* stopAtWindowProxy = */ false); + MOZ_RELEASE_ASSERT(JS_IsGlobalObject(mainGlobal)); + + return WindowOrNull(mainGlobal); +} + +nsGlobalWindow* +CurrentWindowOrNull(JSContext* cx) +{ + JSObject* glob = JS::CurrentGlobalOrNull(cx); + return glob ? WindowOrNull(glob) : nullptr; +} + +} // namespace xpc + +static void +CompartmentDestroyedCallback(JSFreeOp* fop, JSCompartment* compartment) +{ + // NB - This callback may be called in JS_DestroyContext, which happens + // after the XPCJSContext has been torn down. + + // Get the current compartment private into an AutoPtr (which will do the + // cleanup for us), and null out the private (which may already be null). + nsAutoPtr priv(CompartmentPrivate::Get(compartment)); + JS_SetCompartmentPrivate(compartment, nullptr); +} + +static size_t +CompartmentSizeOfIncludingThisCallback(MallocSizeOf mallocSizeOf, JSCompartment* compartment) +{ + CompartmentPrivate* priv = CompartmentPrivate::Get(compartment); + return priv ? priv->SizeOfIncludingThis(mallocSizeOf) : 0; +} + +/* + * Return true if there exists a non-system inner window which is a current + * inner window and whose reflector is gray. We don't merge system + * compartments, so we don't use them to trigger merging CCs. + */ +bool XPCJSContext::UsefulToMergeZones() const +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Turns out, actually making this return true often enough makes Windows + // mochitest-gl OOM a lot. Need to figure out what's going on there; see + // bug 1277036. + + return false; +} + +void XPCJSContext::TraceNativeBlackRoots(JSTracer* trc) +{ + if (AutoMarkingPtr* roots = Get()->mAutoRoots) + roots->TraceJSAll(trc); + + // XPCJSObjectHolders don't participate in cycle collection, so always + // trace them here. + XPCRootSetElem* e; + for (e = mObjectHolderRoots; e; e = e->GetNextRoot()) + static_cast(e)->TraceJS(trc); + + dom::TraceBlackJS(trc, JS_GetGCParameter(Context(), JSGC_NUMBER), + nsXPConnect::XPConnect()->IsShuttingDown()); +} + +void XPCJSContext::TraceAdditionalNativeGrayRoots(JSTracer* trc) +{ + XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc, this); + + for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot()) + static_cast(e)->TraceJS(trc); + + for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot()) + static_cast(e)->TraceJS(trc); +} + +void +XPCJSContext::TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& cb) +{ + XPCWrappedNativeScope::SuspectAllWrappers(this, cb); + + for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot()) { + XPCTraceableVariant* v = static_cast(e); + if (nsCCUncollectableMarker::InGeneration(cb, + v->CCGeneration())) { + JS::Value val = v->GetJSValPreserveColor(); + if (val.isObject() && !JS::ObjectIsMarkedGray(&val.toObject())) + continue; + } + cb.NoteXPCOMRoot(v); + } + + for (XPCRootSetElem* e = mWrappedJSRoots; e ; e = e->GetNextRoot()) { + cb.NoteXPCOMRoot(ToSupports(static_cast(e))); + } +} + +void +XPCJSContext::UnmarkSkippableJSHolders() +{ + CycleCollectedJSContext::UnmarkSkippableJSHolders(); +} + +void +XPCJSContext::PrepareForForgetSkippable() +{ + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "cycle-collector-forget-skippable", nullptr); + } +} + +void +XPCJSContext::BeginCycleCollectionCallback() +{ + nsJSContext::BeginCycleCollectionCallback(); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "cycle-collector-begin", nullptr); + } +} + +void +XPCJSContext::EndCycleCollectionCallback(CycleCollectorResults& aResults) +{ + nsJSContext::EndCycleCollectionCallback(aResults); + + nsCOMPtr obs = mozilla::services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "cycle-collector-end", nullptr); + } +} + +void +XPCJSContext::DispatchDeferredDeletion(bool aContinuation, bool aPurge) +{ + mAsyncSnowWhiteFreer->Dispatch(aContinuation, aPurge); +} + +void +xpc_UnmarkSkippableJSHolders() +{ + if (nsXPConnect::XPConnect()->GetContext()) { + nsXPConnect::XPConnect()->GetContext()->UnmarkSkippableJSHolders(); + } +} + +/* static */ void +XPCJSContext::GCSliceCallback(JSContext* cx, + JS::GCProgress progress, + const JS::GCDescription& desc) +{ + XPCJSContext* self = nsXPConnect::GetContextInstance(); + if (!self) + return; + +#ifdef MOZ_CRASHREPORTER + CrashReporter::SetGarbageCollecting(progress == JS::GC_CYCLE_BEGIN || + progress == JS::GC_SLICE_BEGIN); +#endif + + if (self->mPrevGCSliceCallback) + (*self->mPrevGCSliceCallback)(cx, progress, desc); +} + +/* static */ void +XPCJSContext::DoCycleCollectionCallback(JSContext* cx) +{ + // The GC has detected that a CC at this point would collect a tremendous + // amount of garbage that is being revivified unnecessarily. + NS_DispatchToCurrentThread( + NS_NewRunnableFunction([](){nsJSContext::CycleCollectNow(nullptr);})); + + XPCJSContext* self = nsXPConnect::GetContextInstance(); + if (!self) + return; + + if (self->mPrevDoCycleCollectionCallback) + (*self->mPrevDoCycleCollectionCallback)(cx); +} + +void +XPCJSContext::CustomGCCallback(JSGCStatus status) +{ + nsTArray callbacks(extraGCCallbacks); + for (uint32_t i = 0; i < callbacks.Length(); ++i) + callbacks[i](status); +} + +/* static */ void +XPCJSContext::FinalizeCallback(JSFreeOp* fop, + JSFinalizeStatus status, + bool isZoneGC, + void* data) +{ + XPCJSContext* self = nsXPConnect::GetContextInstance(); + if (!self) + return; + + switch (status) { + case JSFINALIZE_GROUP_START: + { + MOZ_ASSERT(!self->mDoingFinalization, "bad state"); + + MOZ_ASSERT(!self->mGCIsRunning, "bad state"); + self->mGCIsRunning = true; + + self->mDoingFinalization = true; + break; + } + case JSFINALIZE_GROUP_END: + { + MOZ_ASSERT(self->mDoingFinalization, "bad state"); + self->mDoingFinalization = false; + + // Sweep scopes needing cleanup + XPCWrappedNativeScope::KillDyingScopes(); + + MOZ_ASSERT(self->mGCIsRunning, "bad state"); + self->mGCIsRunning = false; + + break; + } + case JSFINALIZE_COLLECTION_END: + { + MOZ_ASSERT(!self->mGCIsRunning, "bad state"); + self->mGCIsRunning = true; + + if (AutoMarkingPtr* roots = Get()->mAutoRoots) + roots->MarkAfterJSFinalizeAll(); + + // Now we are going to recycle any unused WrappedNativeTearoffs. + // We do this by iterating all the live callcontexts + // and marking the tearoffs in use. And then we + // iterate over all the WrappedNative wrappers and sweep their + // tearoffs. + // + // This allows us to perhaps minimize the growth of the + // tearoffs. And also makes us not hold references to interfaces + // on our wrapped natives that we are not actually using. + // + // XXX We may decide to not do this on *every* gc cycle. + + XPCCallContext* ccxp = XPCJSContext::Get()->GetCallContext(); + while (ccxp) { + // Deal with the strictness of callcontext that + // complains if you ask for a tearoff when + // it is in a state where the tearoff could not + // possibly be valid. + if (ccxp->CanGetTearOff()) { + XPCWrappedNativeTearOff* to = + ccxp->GetTearOff(); + if (to) + to->Mark(); + } + ccxp = ccxp->GetPrevCallContext(); + } + + XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs(); + + // Now we need to kill the 'Dying' XPCWrappedNativeProtos. + // We transfered these native objects to this table when their + // JSObject's were finalized. We did not destroy them immediately + // at that point because the ordering of JS finalization is not + // deterministic and we did not yet know if any wrappers that + // might still be referencing the protos where still yet to be + // finalized and destroyed. We *do* know that the protos' + // JSObjects would not have been finalized if there were any + // wrappers that referenced the proto but where not themselves + // slated for finalization in this gc cycle. So... at this point + // we know that any and all wrappers that might have been + // referencing the protos in the dying list are themselves dead. + // So, we can safely delete all the protos in the list. + + for (auto i = self->mDyingWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + delete static_cast(entry->key); + i.Remove(); + } + + MOZ_ASSERT(self->mGCIsRunning, "bad state"); + self->mGCIsRunning = false; + + break; + } + } +} + +/* static */ void +XPCJSContext::WeakPointerZoneGroupCallback(JSContext* cx, void* data) +{ + // Called before each sweeping slice -- after processing any final marking + // triggered by barriers -- to clear out any references to things that are + // about to be finalized and update any pointers to moved GC things. + XPCJSContext* self = static_cast(data); + + self->mWrappedJSMap->UpdateWeakPointersAfterGC(self); + + XPCWrappedNativeScope::UpdateWeakPointersAfterGC(self); +} + +/* static */ void +XPCJSContext::WeakPointerCompartmentCallback(JSContext* cx, JSCompartment* comp, void* data) +{ + // Called immediately after the ZoneGroup weak pointer callback, but only + // once for each compartment that is being swept. + XPCJSContext* self = static_cast(data); + CompartmentPrivate* xpcComp = CompartmentPrivate::Get(comp); + if (xpcComp) + xpcComp->UpdateWeakPointersAfterGC(self); +} + +void +CompartmentPrivate::UpdateWeakPointersAfterGC(XPCJSContext* context) +{ + mWrappedJSMap->UpdateWeakPointersAfterGC(context); +} + +static void WatchdogMain(void* arg); +class Watchdog; +class WatchdogManager; +class AutoLockWatchdog { + Watchdog* const mWatchdog; + public: + explicit AutoLockWatchdog(Watchdog* aWatchdog); + ~AutoLockWatchdog(); +}; + +class Watchdog +{ + public: + explicit Watchdog(WatchdogManager* aManager) + : mManager(aManager) + , mLock(nullptr) + , mWakeup(nullptr) + , mThread(nullptr) + , mHibernating(false) + , mInitialized(false) + , mShuttingDown(false) + , mMinScriptRunTimeSeconds(1) + {} + ~Watchdog() { MOZ_ASSERT(!Initialized()); } + + WatchdogManager* Manager() { return mManager; } + bool Initialized() { return mInitialized; } + bool ShuttingDown() { return mShuttingDown; } + PRLock* GetLock() { return mLock; } + bool Hibernating() { return mHibernating; } + void WakeUp() + { + MOZ_ASSERT(Initialized()); + MOZ_ASSERT(Hibernating()); + mHibernating = false; + PR_NotifyCondVar(mWakeup); + } + + // + // Invoked by the main thread only. + // + + void Init() + { + MOZ_ASSERT(NS_IsMainThread()); + mLock = PR_NewLock(); + if (!mLock) + NS_RUNTIMEABORT("PR_NewLock failed."); + mWakeup = PR_NewCondVar(mLock); + if (!mWakeup) + NS_RUNTIMEABORT("PR_NewCondVar failed."); + + { + AutoLockWatchdog lock(this); + + // Gecko uses thread private for accounting and has to clean up at thread exit. + // Therefore, even though we don't have a return value from the watchdog, we need to + // join it on shutdown. + mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + if (!mThread) + NS_RUNTIMEABORT("PR_CreateThread failed!"); + + // WatchdogMain acquires the lock and then asserts mInitialized. So + // make sure to set mInitialized before releasing the lock here so + // that it's atomic with the creation of the thread. + mInitialized = true; + } + } + + void Shutdown() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(Initialized()); + { // Scoped lock. + AutoLockWatchdog lock(this); + + // Signal to the watchdog thread that it's time to shut down. + mShuttingDown = true; + + // Wake up the watchdog, and wait for it to call us back. + PR_NotifyCondVar(mWakeup); + } + + PR_JoinThread(mThread); + + // The thread sets mShuttingDown to false as it exits. + MOZ_ASSERT(!mShuttingDown); + + // Destroy state. + mThread = nullptr; + PR_DestroyCondVar(mWakeup); + mWakeup = nullptr; + PR_DestroyLock(mLock); + mLock = nullptr; + + // All done. + mInitialized = false; + } + + void SetMinScriptRunTimeSeconds(int32_t seconds) + { + // This variable is atomic, and is set from the main thread without + // locking. + MOZ_ASSERT(seconds > 0); + mMinScriptRunTimeSeconds = seconds; + } + + // + // Invoked by the watchdog thread only. + // + + void Hibernate() + { + MOZ_ASSERT(!NS_IsMainThread()); + mHibernating = true; + Sleep(PR_INTERVAL_NO_TIMEOUT); + } + void Sleep(PRIntervalTime timeout) + { + MOZ_ASSERT(!NS_IsMainThread()); + MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS); + } + void Finished() + { + MOZ_ASSERT(!NS_IsMainThread()); + mShuttingDown = false; + } + + int32_t MinScriptRunTimeSeconds() + { + return mMinScriptRunTimeSeconds; + } + + private: + WatchdogManager* mManager; + + PRLock* mLock; + PRCondVar* mWakeup; + PRThread* mThread; + bool mHibernating; + bool mInitialized; + bool mShuttingDown; + mozilla::Atomic mMinScriptRunTimeSeconds; +}; + +#define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time" +#define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time" + +class WatchdogManager : public nsIObserver +{ + public: + + NS_DECL_ISUPPORTS + explicit WatchdogManager(XPCJSContext* aContext) : mContext(aContext) + , mContextState(CONTEXT_INACTIVE) + { + // All the timestamps start at zero except for context state change. + PodArrayZero(mTimestamps); + mTimestamps[TimestampContextStateChange] = PR_Now(); + + // Enable the watchdog, if appropriate. + RefreshWatchdog(); + + // Register ourselves as an observer to get updates on the pref. + mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog"); + mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT); + mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME); + } + + protected: + + virtual ~WatchdogManager() + { + // Shutting down the watchdog requires context-switching to the watchdog + // thread, which isn't great to do in a destructor. So we require + // consumers to shut it down manually before releasing it. + MOZ_ASSERT(!mWatchdog); + mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog"); + mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT); + mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME); + } + + public: + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override + { + RefreshWatchdog(); + return NS_OK; + } + + // Context statistics. These live on the watchdog manager, are written + // from the main thread, and are read from the watchdog thread (holding + // the lock in each case). + void + RecordContextActivity(bool active) + { + // The watchdog reads this state, so acquire the lock before writing it. + MOZ_ASSERT(NS_IsMainThread()); + Maybe lock; + if (mWatchdog) + lock.emplace(mWatchdog); + + // Write state. + mTimestamps[TimestampContextStateChange] = PR_Now(); + mContextState = active ? CONTEXT_ACTIVE : CONTEXT_INACTIVE; + + // The watchdog may be hibernating, waiting for the context to go + // active. Wake it up if necessary. + if (active && mWatchdog && mWatchdog->Hibernating()) + mWatchdog->WakeUp(); + } + bool IsContextActive() { return mContextState == CONTEXT_ACTIVE; } + PRTime TimeSinceLastContextStateChange() + { + return PR_Now() - GetTimestamp(TimestampContextStateChange); + } + + // Note - Because of the context activity timestamp, these are read and + // written from both threads. + void RecordTimestamp(WatchdogTimestampCategory aCategory) + { + // The watchdog thread always holds the lock when it runs. + Maybe maybeLock; + if (NS_IsMainThread() && mWatchdog) + maybeLock.emplace(mWatchdog); + mTimestamps[aCategory] = PR_Now(); + } + PRTime GetTimestamp(WatchdogTimestampCategory aCategory) + { + // The watchdog thread always holds the lock when it runs. + Maybe maybeLock; + if (NS_IsMainThread() && mWatchdog) + maybeLock.emplace(mWatchdog); + return mTimestamps[aCategory]; + } + + XPCJSContext* Context() { return mContext; } + Watchdog* GetWatchdog() { return mWatchdog; } + + void RefreshWatchdog() + { + bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true); + if (wantWatchdog != !!mWatchdog) { + if (wantWatchdog) + StartWatchdog(); + else + StopWatchdog(); + } + + if (mWatchdog) { + int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10); + if (contentTime <= 0) + contentTime = INT32_MAX; + int32_t chromeTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20); + if (chromeTime <= 0) + chromeTime = INT32_MAX; + mWatchdog->SetMinScriptRunTimeSeconds(std::min(contentTime, chromeTime)); + } + } + + void StartWatchdog() + { + MOZ_ASSERT(!mWatchdog); + mWatchdog = new Watchdog(this); + mWatchdog->Init(); + } + + void StopWatchdog() + { + MOZ_ASSERT(mWatchdog); + mWatchdog->Shutdown(); + mWatchdog = nullptr; + } + + private: + XPCJSContext* mContext; + nsAutoPtr mWatchdog; + + enum { CONTEXT_ACTIVE, CONTEXT_INACTIVE } mContextState; + PRTime mTimestamps[TimestampCount]; +}; + +NS_IMPL_ISUPPORTS(WatchdogManager, nsIObserver) + +AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog) +{ + PR_Lock(mWatchdog->GetLock()); +} + +AutoLockWatchdog::~AutoLockWatchdog() +{ + PR_Unlock(mWatchdog->GetLock()); +} + +static void +WatchdogMain(void* arg) +{ + PR_SetCurrentThreadName("JS Watchdog"); + + Watchdog* self = static_cast(arg); + WatchdogManager* manager = self->Manager(); + + // Lock lasts until we return + AutoLockWatchdog lock(self); + + MOZ_ASSERT(self->Initialized()); + MOZ_ASSERT(!self->ShuttingDown()); + while (!self->ShuttingDown()) { + // Sleep only 1 second if recently (or currently) active; otherwise, hibernate + if (manager->IsContextActive() || + manager->TimeSinceLastContextStateChange() <= PRTime(2*PR_USEC_PER_SEC)) + { + self->Sleep(PR_TicksPerSecond()); + } else { + manager->RecordTimestamp(TimestampWatchdogHibernateStart); + self->Hibernate(); + manager->RecordTimestamp(TimestampWatchdogHibernateStop); + } + + // Rise and shine. + manager->RecordTimestamp(TimestampWatchdogWakeup); + + // Don't request an interrupt callback unless the current script has + // been running long enough that we might show the slow script dialog. + // Triggering the callback from off the main thread can be expensive. + + // We want to avoid showing the slow script dialog if the user's laptop + // goes to sleep in the middle of running a script. To ensure this, we + // invoke the interrupt callback after only half the timeout has + // elapsed. The callback simply records the fact that it was called in + // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2) + // seconds and invoke the callback again. This time around it sees + // mSlowScriptSecondHalf is set and so it shows the slow script + // dialog. If the computer is put to sleep during one of the (timeout/2) + // periods, the script still has the other (timeout/2) seconds to + // finish. + PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2; + if (manager->IsContextActive() && + manager->TimeSinceLastContextStateChange() >= usecs) + { + bool debuggerAttached = false; + nsCOMPtr dbg = do_GetService("@mozilla.org/xpcom/debug;1"); + if (dbg) + dbg->GetIsDebuggerAttached(&debuggerAttached); + if (!debuggerAttached) + JS_RequestInterruptCallback(manager->Context()->Context()); + } + } + + // Tell the manager that we've shut down. + self->Finished(); +} + +PRTime +XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory) +{ + return mWatchdogManager->GetTimestamp(aCategory); +} + +void +xpc::SimulateActivityCallback(bool aActive) +{ + XPCJSContext::ActivityCallback(XPCJSContext::Get(), aActive); +} + +// static +void +XPCJSContext::ActivityCallback(void* arg, bool active) +{ + if (!active) { + ProcessHangMonitor::ClearHang(); + } + + XPCJSContext* self = static_cast(arg); + self->mWatchdogManager->RecordContextActivity(active); +} + +// static +bool +XPCJSContext::InterruptCallback(JSContext* cx) +{ + XPCJSContext* self = XPCJSContext::Get(); + + // Normally we record mSlowScriptCheckpoint when we start to process an + // event. However, we can run JS outside of event handlers. This code takes + // care of that case. + if (self->mSlowScriptCheckpoint.IsNull()) { + self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); + self->mSlowScriptSecondHalf = false; + self->mSlowScriptActualWait = mozilla::TimeDuration(); + self->mTimeoutAccumulated = false; + return true; + } + + // Sometimes we get called back during XPConnect initialization, before Gecko + // has finished bootstrapping. Avoid crashing in nsContentUtils below. + if (!nsContentUtils::IsInitialized()) + return true; + + // This is at least the second interrupt callback we've received since + // returning to the event loop. See how long it's been, and what the limit + // is. + TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint; + bool chrome = nsContentUtils::IsCallerChrome(); + const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME + : PREF_MAX_SCRIPT_RUN_TIME_CONTENT; + int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10); + + // If there's no limit, or we're within the limit, let it go. + if (limit == 0 || duration.ToSeconds() < limit / 2.0) + return true; + + self->mSlowScriptActualWait += duration; + + // In order to guard against time changes or laptops going to sleep, we + // don't trigger the slow script warning until (limit/2) seconds have + // elapsed twice. + if (!self->mSlowScriptSecondHalf) { + self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); + self->mSlowScriptSecondHalf = true; + return true; + } + + // + // This has gone on long enough! Time to take action. ;-) + // + + // Get the DOM window associated with the running script. If the script is + // running in a non-DOM scope, we have to just let it keep running. + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + RefPtr win = WindowOrNull(global); + if (!win && IsSandbox(global)) { + // If this is a sandbox associated with a DOMWindow via a + // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey + // and JetPack content scripts. + JS::Rooted proto(cx); + if (!JS_GetPrototype(cx, global, &proto)) + return false; + if (proto && IsSandboxPrototypeProxy(proto) && + (proto = js::CheckedUnwrap(proto, /* stopAtWindowProxy = */ false))) + { + win = WindowGlobalOrNull(proto); + } + } + + if (!win) { + NS_WARNING("No active window"); + return true; + } + + if (win->IsDying()) { + // The window is being torn down. When that happens we try to prevent + // the dispatch of new runnables, so it also makes sense to kill any + // long-running script. The user is primarily interested in this page + // going away. + return false; + } + + if (win->GetIsPrerendered()) { + // We cannot display a dialog if the page is being prerendered, so + // just kill the page. + mozilla::dom::HandlePrerenderingViolation(win->AsInner()); + return false; + } + + // Accumulate slow script invokation delay. + if (!chrome && !self->mTimeoutAccumulated) { + uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0)); + Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay); + self->mTimeoutAccumulated = true; + } + + // Show the prompt to the user, and kill if requested. + nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog(); + if (response == nsGlobalWindow::KillSlowScript) { + if (Preferences::GetBool("dom.global_stop_script", true)) + xpc::Scriptability::Get(global).Block(); + return false; + } + + // The user chose to continue the script. Reset the timer, and disable this + // machinery with a pref of the user opted out of future slow-script dialogs. + if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying) + self->mSlowScriptCheckpoint = TimeStamp::NowLoRes(); + + if (response == nsGlobalWindow::AlwaysContinueSlowScript) + Preferences::SetInt(prefName, 0); + + return true; +} + +void +XPCJSContext::CustomOutOfMemoryCallback() +{ + if (!Preferences::GetBool("memory.dump_reports_on_oom")) { + return; + } + + nsCOMPtr dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + if (!dumper) { + return; + } + + // If this fails, it fails silently. + dumper->DumpMemoryInfoToTempDir(NS_LITERAL_STRING("due-to-JS-OOM"), + /* anonymize = */ false, + /* minimizeMemoryUsage = */ false); +} + +void +XPCJSContext::CustomLargeAllocationFailureCallback() +{ + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); + } +} + +size_t +XPCJSContext::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) +{ + size_t n = 0; + n += mallocSizeOf(this); + n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); + n += mIID2NativeInterfaceMap->SizeOfIncludingThis(mallocSizeOf); + n += mClassInfo2NativeSetMap->ShallowSizeOfIncludingThis(mallocSizeOf); + n += mNativeSetMap->SizeOfIncludingThis(mallocSizeOf); + + n += CycleCollectedJSContext::SizeOfExcludingThis(mallocSizeOf); + + // There are other XPCJSContext members that could be measured; the above + // ones have been seen by DMD to be worth measuring. More stuff may be + // added later. + + return n; +} + +size_t +CompartmentPrivate::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) +{ + size_t n = mallocSizeOf(this); + n += mWrappedJSMap->SizeOfIncludingThis(mallocSizeOf); + n += mWrappedJSMap->SizeOfWrappedJS(mallocSizeOf); + return n; +} + +/***************************************************************************/ + +#define JS_OPTIONS_DOT_STR "javascript.options." + +static void +ReloadPrefsCallback(const char* pref, void* data) +{ + XPCJSContext* xpccx = reinterpret_cast(data); + JSContext* cx = xpccx->Context(); + + bool safeMode = false; + nsCOMPtr xr = do_GetService("@mozilla.org/xre/runtime;1"); + if (xr) { + xr->GetInSafeMode(&safeMode); + } + + bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode; + bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode; + bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode; + bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm") && !safeMode; + bool useWasmBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit") && !safeMode; + bool throwOnAsmJSValidationFailure = Preferences::GetBool(JS_OPTIONS_DOT_STR + "throw_on_asmjs_validation_failure"); + bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp") && !safeMode; + + bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing"); + bool offthreadIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR + "ion.offthread_compilation"); + bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR + "baselinejit.unsafe_eager_compilation"); + bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation"); + + sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource"); + + bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack"); + + bool throwOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR + "throw_on_debuggee_would_run"); + + bool dumpStackOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR + "dump_stack_on_debuggee_would_run"); + + bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror"); + + bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict"); + + sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory"); + +#ifdef DEBUG + sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug"); +#endif + +#ifdef JS_GC_ZEAL + int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1); + int32_t zeal_frequency = + Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal.frequency", + JS_DEFAULT_ZEAL_FREQ); + if (zeal >= 0) { + JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency); + } +#endif // JS_GC_ZEAL + + JS::ContextOptionsRef(cx).setBaseline(useBaseline) + .setIon(useIon) + .setAsmJS(useAsmJS) + .setWasm(useWasm) + .setWasmAlwaysBaseline(useWasmBaseline) + .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure) + .setNativeRegExp(useNativeRegExp) + .setAsyncStack(useAsyncStack) + .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun) + .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun) + .setWerror(werror) + .setExtraWarnings(extraWarnings); + + JS_SetParallelParsingEnabled(cx, parallelParsing); + JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER, + useBaselineEager ? 0 : -1); + JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER, + useIonEager ? 0 : -1); +} + +XPCJSContext::~XPCJSContext() +{ + // Elsewhere we abort immediately if XPCJSContext initialization fails. + // Therefore the context must be non-null. + MOZ_ASSERT(MaybeContext()); + + // This destructor runs before ~CycleCollectedJSContext, which does the + // actual JS_DestroyContext() call. But destroying the context triggers + // one final GC, which can call back into the context with various + // callbacks if we aren't careful. Null out the relevant callbacks. + js::SetActivityCallback(Context(), nullptr, nullptr); + JS_RemoveFinalizeCallback(Context(), FinalizeCallback); + JS_RemoveWeakPointerZoneGroupCallback(Context(), WeakPointerZoneGroupCallback); + JS_RemoveWeakPointerCompartmentCallback(Context(), WeakPointerCompartmentCallback); + + // Clear any pending exception. It might be an XPCWrappedJS, and if we try + // to destroy it later we will crash. + SetPendingException(nullptr); + + JS::SetGCSliceCallback(Context(), mPrevGCSliceCallback); + + xpc_DelocalizeContext(Context()); + + if (mWatchdogManager->GetWatchdog()) + mWatchdogManager->StopWatchdog(); + + if (mCallContext) + mCallContext->SystemIsBeingShutDown(); + + auto rtPrivate = static_cast(JS_GetContextPrivate(Context())); + delete rtPrivate; + JS_SetContextPrivate(Context(), nullptr); + + // clean up and destroy maps... + mWrappedJSMap->ShutdownMarker(); + delete mWrappedJSMap; + mWrappedJSMap = nullptr; + + delete mWrappedJSClassMap; + mWrappedJSClassMap = nullptr; + + delete mIID2NativeInterfaceMap; + mIID2NativeInterfaceMap = nullptr; + + delete mClassInfo2NativeSetMap; + mClassInfo2NativeSetMap = nullptr; + + delete mNativeSetMap; + mNativeSetMap = nullptr; + + delete mThisTranslatorMap; + mThisTranslatorMap = nullptr; + + delete mDyingWrappedNativeProtoMap; + mDyingWrappedNativeProtoMap = nullptr; + +#ifdef MOZ_ENABLE_PROFILER_SPS + // Tell the profiler that the context is gone + if (PseudoStack* stack = mozilla_get_pseudo_stack()) + stack->sampleContext(nullptr); +#endif + + Preferences::UnregisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); +} + +// If |*anonymizeID| is non-zero and this is a user compartment, the name will +// be anonymized. +static void +GetCompartmentName(JSCompartment* c, nsCString& name, int* anonymizeID, + bool replaceSlashes) +{ + if (js::IsAtomsCompartment(c)) { + name.AssignLiteral("atoms"); + } else if (*anonymizeID && !js::IsSystemCompartment(c)) { + name.AppendPrintf("", *anonymizeID); + *anonymizeID += 1; + } else if (JSPrincipals* principals = JS_GetCompartmentPrincipals(c)) { + nsresult rv = nsJSPrincipals::get(principals)->GetScriptLocation(name); + if (NS_FAILED(rv)) { + name.AssignLiteral("(unknown)"); + } + + // If the compartment's location (name) differs from the principal's + // script location, append the compartment's location to allow + // differentiation of multiple compartments owned by the same principal + // (e.g. components owned by the system or null principal). + CompartmentPrivate* compartmentPrivate = CompartmentPrivate::Get(c); + if (compartmentPrivate) { + const nsACString& location = compartmentPrivate->GetLocation(); + if (!location.IsEmpty() && !location.Equals(name)) { + name.AppendLiteral(", "); + name.Append(location); + } + } + + if (*anonymizeID) { + // We might have a file:// URL that includes a path from the local + // filesystem, which should be omitted if we're anonymizing. + static const char* filePrefix = "file://"; + int filePos = name.Find(filePrefix); + if (filePos >= 0) { + int pathPos = filePos + strlen(filePrefix); + int lastSlashPos = -1; + for (int i = pathPos; i < int(name.Length()); i++) { + if (name[i] == '/' || name[i] == '\\') { + lastSlashPos = i; + } + } + if (lastSlashPos != -1) { + name.ReplaceASCII(pathPos, lastSlashPos - pathPos, + ""); + } else { + // Something went wrong. Anonymize the entire path to be + // safe. + name.Truncate(pathPos); + name += ""; + } + } + + // We might have a location like this: + // inProcessTabChildGlobal?ownedBy=http://www.example.com/ + // The owner should be omitted if it's not a chrome: URI and we're + // anonymizing. + static const char* ownedByPrefix = + "inProcessTabChildGlobal?ownedBy="; + int ownedByPos = name.Find(ownedByPrefix); + if (ownedByPos >= 0) { + const char* chrome = "chrome:"; + int ownerPos = ownedByPos + strlen(ownedByPrefix); + const nsDependentCSubstring& ownerFirstPart = + Substring(name, ownerPos, strlen(chrome)); + if (!ownerFirstPart.EqualsASCII(chrome)) { + name.Truncate(ownerPos); + name += ""; + } + } + } + + // A hack: replace forward slashes with '\\' so they aren't + // treated as path separators. Users of the reporters + // (such as about:memory) have to undo this change. + if (replaceSlashes) + name.ReplaceChar('/', '\\'); + } else { + name.AssignLiteral("null-principal"); + } +} + +extern void +xpc::GetCurrentCompartmentName(JSContext* cx, nsCString& name) +{ + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + if (!global) { + name.AssignLiteral("no global"); + return; + } + + JSCompartment* compartment = GetObjectCompartment(global); + int anonymizeID = 0; + GetCompartmentName(compartment, name, &anonymizeID, false); +} + +void +xpc::AddGCCallback(xpcGCCallback cb) +{ + XPCJSContext::Get()->AddGCCallback(cb); +} + +void +xpc::RemoveGCCallback(xpcGCCallback cb) +{ + XPCJSContext::Get()->RemoveGCCallback(cb); +} + +static int64_t +JSMainRuntimeGCHeapDistinguishedAmount() +{ + JSContext* cx = danger::GetJSContext(); + return int64_t(JS_GetGCParameter(cx, JSGC_TOTAL_CHUNKS)) * + js::gc::ChunkSize; +} + +static int64_t +JSMainRuntimeTemporaryPeakDistinguishedAmount() +{ + JSContext* cx = danger::GetJSContext(); + return JS::PeakSizeOfTemporary(cx); +} + +static int64_t +JSMainRuntimeCompartmentsSystemDistinguishedAmount() +{ + JSContext* cx = danger::GetJSContext(); + return JS::SystemCompartmentCount(cx); +} + +static int64_t +JSMainRuntimeCompartmentsUserDistinguishedAmount() +{ + JSContext* cx = nsXPConnect::GetContextInstance()->Context(); + return JS::UserCompartmentCount(cx); +} + +class JSMainRuntimeTemporaryPeakReporter final : public nsIMemoryReporter +{ + ~JSMainRuntimeTemporaryPeakReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + MOZ_COLLECT_REPORT( + "js-main-runtime-temporary-peak", KIND_OTHER, UNITS_BYTES, + JSMainRuntimeTemporaryPeakDistinguishedAmount(), + "Peak transient data size in the main JSRuntime (the current size " + "of which is reported as " + "'explicit/js-non-window/runtime/temporary')."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(JSMainRuntimeTemporaryPeakReporter, nsIMemoryReporter) + +// The REPORT* macros do an unconditional report. The ZCREPORT* macros are for +// compartments and zones; they aggregate any entries smaller than +// SUNDRIES_THRESHOLD into the "sundries/gc-heap" and "sundries/malloc-heap" +// entries for the compartment. + +#define SUNDRIES_THRESHOLD js::MemoryReportingSundriesThreshold() + +#define REPORT(_path, _kind, _units, _amount, _desc) \ + handleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::_kind, \ + nsIMemoryReporter::_units, _amount, \ + NS_LITERAL_CSTRING(_desc), data); \ + +#define REPORT_BYTES(_path, _kind, _amount, _desc) \ + REPORT(_path, _kind, UNITS_BYTES, _amount, _desc); + +#define REPORT_GC_BYTES(_path, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + handleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::KIND_NONHEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + NS_LITERAL_CSTRING(_desc), data); \ + gcTotal += amount; \ + } while (0) + +// Report compartment/zone non-GC (KIND_HEAP) bytes. +#define ZCREPORT_BYTES(_path, _amount, _desc) \ + do { \ + /* Assign _descLiteral plus "" into a char* to prove that it's */ \ + /* actually a literal. */ \ + size_t amount = _amount; /* evaluate _amount only once */ \ + if (amount >= SUNDRIES_THRESHOLD) { \ + handleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::KIND_HEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + NS_LITERAL_CSTRING(_desc), data); \ + } else { \ + sundriesMallocHeap += amount; \ + } \ + } while (0) + +// Report compartment/zone GC bytes. +#define ZCREPORT_GC_BYTES(_path, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + if (amount >= SUNDRIES_THRESHOLD) { \ + handleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::KIND_NONHEAP, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + NS_LITERAL_CSTRING(_desc), data); \ + gcTotal += amount; \ + } else { \ + sundriesGCHeap += amount; \ + } \ + } while (0) + +// Report runtime bytes. +#define RREPORT_BYTES(_path, _kind, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + handleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::_kind, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + NS_LITERAL_CSTRING(_desc), data); \ + rtTotal += amount; \ + } while (0) + +// Report GC thing bytes. +#define MREPORT_BYTES(_path, _kind, _amount, _desc) \ + do { \ + size_t amount = _amount; /* evaluate _amount only once */ \ + handleReport->Callback(EmptyCString(), _path, \ + nsIMemoryReporter::_kind, \ + nsIMemoryReporter::UNITS_BYTES, amount, \ + NS_LITERAL_CSTRING(_desc), data); \ + gcThingTotal += amount; \ + } while (0) + +MOZ_DEFINE_MALLOC_SIZE_OF(JSMallocSizeOf) + +namespace xpc { + +static void +ReportZoneStats(const JS::ZoneStats& zStats, + const xpc::ZoneStatsExtras& extras, + nsIHandleReportCallback* handleReport, + nsISupports* data, + bool anonymize, + size_t* gcTotalOut = nullptr) +{ + const nsCString& pathPrefix = extras.pathPrefix; + size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; + + MOZ_ASSERT(!gcTotalOut == zStats.isTotals); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("symbols/gc-heap"), + zStats.symbolsGCHeap, + "Symbols."); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("gc-heap-arena-admin"), + zStats.gcHeapArenaAdmin, + "Bookkeeping information and alignment padding within GC arenas."); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("unused-gc-things"), + zStats.unusedGCThings.totalSize(), + "Unused GC thing cells within non-empty arenas."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("unique-id-map"), + zStats.uniqueIdMap, + "Address-independent cell identities."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shape-tables"), + zStats.shapeTables, + "Tables storing shape information."); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/gc-heap"), + zStats.lazyScriptsGCHeap, + "Scripts that haven't executed yet."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("lazy-scripts/malloc-heap"), + zStats.lazyScriptsMallocHeap, + "Lazy script tables containing closed-over bindings or inner functions."); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("jit-codes-gc-heap"), + zStats.jitCodesGCHeap, + "References to executable code pools used by the JITs."); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("object-groups/gc-heap"), + zStats.objectGroupsGCHeap, + "Classification and type inference information about objects."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("object-groups/malloc-heap"), + zStats.objectGroupsMallocHeap, + "Object group addenda."); + + ZCREPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/gc-heap"), + zStats.scopesGCHeap, + "Scope information for scripts."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("scopes/malloc-heap"), + zStats.scopesMallocHeap, + "Arrays of binding names and other binding-related data."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("type-pool"), + zStats.typePool, + "Type sets and related data."); + + ZCREPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("baseline/optimized-stubs"), + zStats.baselineStubsOptimized, + "The Baseline JIT's optimized IC stubs (excluding code)."); + + size_t stringsNotableAboutMemoryGCHeap = 0; + size_t stringsNotableAboutMemoryMallocHeap = 0; + + #define MAYBE_INLINE \ + "The characters may be inline or on the malloc heap." + #define MAYBE_OVERALLOCATED \ + "Sometimes over-allocated to simplify string concatenation." + + for (size_t i = 0; i < zStats.notableStrings.length(); i++) { + const JS::NotableStringInfo& info = zStats.notableStrings[i]; + + MOZ_ASSERT(!zStats.isTotals); + + // We don't do notable string detection when anonymizing, because + // there's a good chance its for crash submission, and the memory + // required for notable string detection is high. + MOZ_ASSERT(!anonymize); + + nsDependentCString notableString(info.buffer); + + // Viewing about:memory generates many notable strings which contain + // "string(length=". If we report these as notable, then we'll create + // even more notable strings the next time we open about:memory (unless + // there's a GC in the meantime), and so on ad infinitum. + // + // To avoid cluttering up about:memory like this, we stick notable + // strings which contain "string(length=" into their own bucket. +# define STRING_LENGTH "string(length=" + if (FindInReadable(NS_LITERAL_CSTRING(STRING_LENGTH), notableString)) { + stringsNotableAboutMemoryGCHeap += info.gcHeapLatin1; + stringsNotableAboutMemoryGCHeap += info.gcHeapTwoByte; + stringsNotableAboutMemoryMallocHeap += info.mallocHeapLatin1; + stringsNotableAboutMemoryMallocHeap += info.mallocHeapTwoByte; + continue; + } + + // Escape / to \ before we put notableString into the memory reporter + // path, because we don't want any forward slashes in the string to + // count as path separators. + nsCString escapedString(notableString); + escapedString.ReplaceSubstring("/", "\\"); + + bool truncated = notableString.Length() < info.length; + + nsCString path = pathPrefix + + nsPrintfCString("strings/" STRING_LENGTH "%d, copies=%d, \"%s\"%s)/", + info.length, info.numCopies, escapedString.get(), + truncated ? " (truncated)" : ""); + + if (info.gcHeapLatin1 > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap/latin1"), + info.gcHeapLatin1, + "Latin1 strings. " MAYBE_INLINE); + } + + if (info.gcHeapTwoByte > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("gc-heap/two-byte"), + info.gcHeapTwoByte, + "TwoByte strings. " MAYBE_INLINE); + } + + if (info.mallocHeapLatin1 > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap/latin1"), + KIND_HEAP, info.mallocHeapLatin1, + "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED); + } + + if (info.mallocHeapTwoByte > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("malloc-heap/two-byte"), + KIND_HEAP, info.mallocHeapTwoByte, + "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED); + } + } + + nsCString nonNotablePath = pathPrefix; + nonNotablePath += (zStats.isTotals || anonymize) + ? NS_LITERAL_CSTRING("strings/") + : NS_LITERAL_CSTRING("strings/string()/"); + + if (zStats.stringInfo.gcHeapLatin1 > 0) { + REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap/latin1"), + zStats.stringInfo.gcHeapLatin1, + "Latin1 strings. " MAYBE_INLINE); + } + + if (zStats.stringInfo.gcHeapTwoByte > 0) { + REPORT_GC_BYTES(nonNotablePath + NS_LITERAL_CSTRING("gc-heap/two-byte"), + zStats.stringInfo.gcHeapTwoByte, + "TwoByte strings. " MAYBE_INLINE); + } + + if (zStats.stringInfo.mallocHeapLatin1 > 0) { + REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap/latin1"), + KIND_HEAP, zStats.stringInfo.mallocHeapLatin1, + "Non-inline Latin1 string characters. " MAYBE_OVERALLOCATED); + } + + if (zStats.stringInfo.mallocHeapTwoByte > 0) { + REPORT_BYTES(nonNotablePath + NS_LITERAL_CSTRING("malloc-heap/two-byte"), + KIND_HEAP, zStats.stringInfo.mallocHeapTwoByte, + "Non-inline TwoByte string characters. " MAYBE_OVERALLOCATED); + } + + if (stringsNotableAboutMemoryGCHeap > 0) { + MOZ_ASSERT(!zStats.isTotals); + REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string()/gc-heap"), + stringsNotableAboutMemoryGCHeap, + "Strings that contain the characters '" STRING_LENGTH "', which " + "are probably from about:memory itself." MAYBE_INLINE + " We filter them out rather than display them, because displaying " + "them would create even more such strings every time about:memory " + "is refreshed."); + } + + if (stringsNotableAboutMemoryMallocHeap > 0) { + MOZ_ASSERT(!zStats.isTotals); + REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("strings/string()/malloc-heap"), + KIND_HEAP, stringsNotableAboutMemoryMallocHeap, + "Non-inline string characters of strings that contain the " + "characters '" STRING_LENGTH "', which are probably from " + "about:memory itself. " MAYBE_OVERALLOCATED + " We filter them out rather than display them, because displaying " + "them would create even more such strings every time about:memory " + "is refreshed."); + } + + const JS::ShapeInfo& shapeInfo = zStats.shapeInfo; + if (shapeInfo.shapesGCHeapTree > 0) { + REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/tree"), + shapeInfo.shapesGCHeapTree, + "Shapes in a property tree."); + } + + if (shapeInfo.shapesGCHeapDict > 0) { + REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/dict"), + shapeInfo.shapesGCHeapDict, + "Shapes in dictionary mode."); + } + + if (shapeInfo.shapesGCHeapBase > 0) { + REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/gc-heap/base"), + shapeInfo.shapesGCHeapBase, + "Base shapes, which collate data common to many shapes."); + } + + if (shapeInfo.shapesMallocHeapTreeTables > 0) { + REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-tables"), + KIND_HEAP, shapeInfo.shapesMallocHeapTreeTables, + "Property tables of shapes in a property tree."); + } + + if (shapeInfo.shapesMallocHeapDictTables > 0) { + REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/dict-tables"), + KIND_HEAP, shapeInfo.shapesMallocHeapDictTables, + "Property tables of shapes in dictionary mode."); + } + + if (shapeInfo.shapesMallocHeapTreeKids > 0) { + REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("shapes/malloc-heap/tree-kids"), + KIND_HEAP, shapeInfo.shapesMallocHeapTreeKids, + "Kid hashes of shapes in a property tree."); + } + + if (sundriesGCHeap > 0) { + // We deliberately don't use ZCREPORT_GC_BYTES here. + REPORT_GC_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"), + sundriesGCHeap, + "The sum of all 'gc-heap' measurements that are too small to be " + "worth showing individually."); + } + + if (sundriesMallocHeap > 0) { + // We deliberately don't use ZCREPORT_BYTES here. + REPORT_BYTES(pathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"), + KIND_HEAP, sundriesMallocHeap, + "The sum of all 'malloc-heap' measurements that are too small to " + "be worth showing individually."); + } + + if (gcTotalOut) + *gcTotalOut += gcTotal; + +# undef STRING_LENGTH +} + +static void +ReportClassStats(const ClassInfo& classInfo, const nsACString& path, + nsIHandleReportCallback* handleReport, + nsISupports* data, size_t& gcTotal) +{ + // We deliberately don't use ZCREPORT_BYTES, so that these per-class values + // don't go into sundries. + + if (classInfo.objectsGCHeap > 0) { + REPORT_GC_BYTES(path + NS_LITERAL_CSTRING("objects/gc-heap"), + classInfo.objectsGCHeap, + "Objects, including fixed slots."); + } + + if (classInfo.objectsMallocHeapSlots > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/slots"), + KIND_HEAP, classInfo.objectsMallocHeapSlots, + "Non-fixed object slots."); + } + + if (classInfo.objectsMallocHeapElementsNormal > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/normal"), + KIND_HEAP, classInfo.objectsMallocHeapElementsNormal, + "Normal (non-wasm) indexed elements."); + } + + if (classInfo.objectsMallocHeapElementsAsmJS > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/elements/asm.js"), + KIND_HEAP, classInfo.objectsMallocHeapElementsAsmJS, + "asm.js array buffer elements allocated in the malloc heap."); + } + + if (classInfo.objectsMallocHeapMisc > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/malloc-heap/misc"), + KIND_HEAP, classInfo.objectsMallocHeapMisc, + "Miscellaneous object data."); + } + + if (classInfo.objectsNonHeapElementsNormal > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/normal"), + KIND_NONHEAP, classInfo.objectsNonHeapElementsNormal, + "Memory-mapped non-shared array buffer elements."); + } + + if (classInfo.objectsNonHeapElementsShared > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/shared"), + KIND_NONHEAP, classInfo.objectsNonHeapElementsShared, + "Memory-mapped shared array buffer elements. These elements are " + "shared between one or more runtimes; the reported size is divided " + "by the buffer's refcount."); + } + + // WebAssembly memories are always non-heap-allocated (mmap). We never put + // these under sundries, because (a) in practice they're almost always + // larger than the sundries threshold, and (b) we'd need a third category of + // sundries ("non-heap"), which would be a pain. + if (classInfo.objectsNonHeapElementsWasm > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/elements/wasm"), + KIND_NONHEAP, classInfo.objectsNonHeapElementsWasm, + "wasm/asm.js array buffer elements allocated outside both the " + "malloc heap and the GC heap."); + } + + if (classInfo.objectsNonHeapCodeWasm > 0) { + REPORT_BYTES(path + NS_LITERAL_CSTRING("objects/non-heap/code/wasm"), + KIND_NONHEAP, classInfo.objectsNonHeapCodeWasm, + "AOT-compiled wasm/asm.js code."); + } + + // Although wasm guard pages aren't committed in memory they can be very + // large and contribute greatly to vsize and so are worth reporting. + if (classInfo.wasmGuardPages > 0) { + REPORT_BYTES(NS_LITERAL_CSTRING("wasm-guard-pages"), + KIND_OTHER, classInfo.wasmGuardPages, + "Guard pages mapped after the end of wasm memories, reserved for " + "optimization tricks, but not committed and thus never contributing" + " to RSS, only vsize."); + } +} + +static void +ReportCompartmentStats(const JS::CompartmentStats& cStats, + const xpc::CompartmentStatsExtras& extras, + amIAddonManager* addonManager, + nsIHandleReportCallback* handleReport, + nsISupports* data, size_t* gcTotalOut = nullptr) +{ + static const nsDependentCString addonPrefix("explicit/add-ons/"); + + size_t gcTotal = 0, sundriesGCHeap = 0, sundriesMallocHeap = 0; + nsAutoCString cJSPathPrefix(extras.jsPathPrefix); + nsAutoCString cDOMPathPrefix(extras.domPathPrefix); + + MOZ_ASSERT(!gcTotalOut == cStats.isTotals); + + // Only attempt to prefix if we got a location and the path wasn't already + // prefixed. + if (extras.location && addonManager && + cJSPathPrefix.Find(addonPrefix, false, 0, 0) != 0) { + nsAutoCString addonId; + bool ok; + if (NS_SUCCEEDED(addonManager->MapURIToAddonID(extras.location, + addonId, &ok)) + && ok) { + // Insert the add-on id as "add-ons/@id@/" after "explicit/" to + // aggregate add-on compartments. + static const size_t explicitLength = strlen("explicit/"); + addonId.Insert(NS_LITERAL_CSTRING("add-ons/"), 0); + addonId += "/"; + cJSPathPrefix.Insert(addonId, explicitLength); + cDOMPathPrefix.Insert(addonId, explicitLength); + } + } + + nsCString nonNotablePath = cJSPathPrefix; + nonNotablePath += cStats.isTotals + ? NS_LITERAL_CSTRING("classes/") + : NS_LITERAL_CSTRING("classes/class()/"); + + ReportClassStats(cStats.classInfo, nonNotablePath, handleReport, data, + gcTotal); + + for (size_t i = 0; i < cStats.notableClasses.length(); i++) { + MOZ_ASSERT(!cStats.isTotals); + const JS::NotableClassInfo& classInfo = cStats.notableClasses[i]; + + nsCString classPath = cJSPathPrefix + + nsPrintfCString("classes/class(%s)/", classInfo.className_); + + ReportClassStats(classInfo, classPath, handleReport, data, gcTotal); + } + + // Note that we use cDOMPathPrefix here. This is because we measure orphan + // DOM nodes in the JS reporter, but we want to report them in a "dom" + // sub-tree rather than a "js" sub-tree. + ZCREPORT_BYTES(cDOMPathPrefix + NS_LITERAL_CSTRING("orphan-nodes"), + cStats.objectsPrivate, + "Orphan DOM nodes, i.e. those that are only reachable from JavaScript " + "objects."); + + ZCREPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/gc-heap"), + cStats.scriptsGCHeap, + "JSScript instances. There is one per user-defined function in a " + "script, and one for the top-level code in a script."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("scripts/malloc-heap/data"), + cStats.scriptsMallocHeapData, + "Various variable-length tables in JSScripts."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/data"), + cStats.baselineData, + "The Baseline JIT's compilation data (BaselineScripts)."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("baseline/fallback-stubs"), + cStats.baselineStubsFallback, + "The Baseline JIT's fallback IC stubs (excluding code)."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("ion-data"), + cStats.ionData, + "The IonMonkey JIT's compilation data (IonScripts)."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/type-scripts"), + cStats.typeInferenceTypeScripts, + "Type sets associated with scripts."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/allocation-site-tables"), + cStats.typeInferenceAllocationSiteTables, + "Tables of type objects associated with allocation sites."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/array-type-tables"), + cStats.typeInferenceArrayTypeTables, + "Tables of type objects associated with array literals."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("type-inference/object-type-tables"), + cStats.typeInferenceObjectTypeTables, + "Tables of type objects associated with object literals."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-object"), + cStats.compartmentObject, + "The JSCompartment object itself."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("compartment-tables"), + cStats.compartmentTables, + "Compartment-wide tables storing object group information and wasm instances."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("inner-views"), + cStats.innerViewsTable, + "The table for array buffer inner views."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("lazy-array-buffers"), + cStats.lazyArrayBuffersTable, + "The table for typed object lazy array buffers."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("object-metadata"), + cStats.objectMetadataTable, + "The table used by debugging tools for tracking object metadata"); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("cross-compartment-wrapper-table"), + cStats.crossCompartmentWrappersTable, + "The cross-compartment wrapper table."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("regexp-compartment"), + cStats.regexpCompartment, + "The regexp compartment and regexp data."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("saved-stacks-set"), + cStats.savedStacksSet, + "The saved stacks set."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("non-syntactic-lexical-scopes-table"), + cStats.nonSyntacticLexicalScopesTable, + "The non-syntactic lexical scopes table."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("jit-compartment"), + cStats.jitCompartment, + "The JIT compartment."); + + ZCREPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("private-data"), + cStats.privateData, + "Extra data attached to the compartment by XPConnect, including " + "its wrapped-js."); + + if (sundriesGCHeap > 0) { + // We deliberately don't use ZCREPORT_GC_BYTES here. + REPORT_GC_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/gc-heap"), + sundriesGCHeap, + "The sum of all 'gc-heap' measurements that are too small to be " + "worth showing individually."); + } + + if (sundriesMallocHeap > 0) { + // We deliberately don't use ZCREPORT_BYTES here. + REPORT_BYTES(cJSPathPrefix + NS_LITERAL_CSTRING("sundries/malloc-heap"), + KIND_HEAP, sundriesMallocHeap, + "The sum of all 'malloc-heap' measurements that are too small to " + "be worth showing individually."); + } + + if (gcTotalOut) + *gcTotalOut += gcTotal; +} + +static void +ReportScriptSourceStats(const ScriptSourceInfo& scriptSourceInfo, + const nsACString& path, + nsIHandleReportCallback* handleReport, + nsISupports* data, size_t& rtTotal) +{ + if (scriptSourceInfo.misc > 0) { + RREPORT_BYTES(path + NS_LITERAL_CSTRING("misc"), + KIND_HEAP, scriptSourceInfo.misc, + "Miscellaneous data relating to JavaScript source code."); + } +} + +static void +ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, + const nsACString& rtPath, + amIAddonManager* addonManager, + nsIHandleReportCallback* handleReport, + nsISupports* data, + bool anonymize, + size_t* rtTotalOut) +{ + size_t gcTotal = 0; + + for (size_t i = 0; i < rtStats.zoneStatsVector.length(); i++) { + const JS::ZoneStats& zStats = rtStats.zoneStatsVector[i]; + const xpc::ZoneStatsExtras* extras = + static_cast(zStats.extra); + ReportZoneStats(zStats, *extras, handleReport, data, anonymize, + &gcTotal); + } + + for (size_t i = 0; i < rtStats.compartmentStatsVector.length(); i++) { + const JS::CompartmentStats& cStats = rtStats.compartmentStatsVector[i]; + const xpc::CompartmentStatsExtras* extras = + static_cast(cStats.extra); + + ReportCompartmentStats(cStats, *extras, addonManager, handleReport, + data, &gcTotal); + } + + // Report the rtStats.runtime numbers under "runtime/", and compute their + // total for later. + + size_t rtTotal = 0; + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/runtime-object"), + KIND_HEAP, rtStats.runtime.object, + "The JSRuntime object."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/atoms-table"), + KIND_HEAP, rtStats.runtime.atomsTable, + "The atoms table."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/contexts"), + KIND_HEAP, rtStats.runtime.contexts, + "JSContext objects and structures that belong to them."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/temporary"), + KIND_HEAP, rtStats.runtime.temporary, + "Transient data (mostly parse nodes) held by the JSRuntime during " + "compilation."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/interpreter-stack"), + KIND_HEAP, rtStats.runtime.interpreterStack, + "JS interpreter frames."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/math-cache"), + KIND_HEAP, rtStats.runtime.mathCache, + "The math cache."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-immutable-strings-cache"), + KIND_HEAP, rtStats.runtime.sharedImmutableStringsCache, + "Immutable strings (such as JS scripts' source text) shared across all JSRuntimes."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/shared-intl-data"), + KIND_HEAP, rtStats.runtime.sharedIntlData, + "Shared internationalization data."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/uncompressed-source-cache"), + KIND_HEAP, rtStats.runtime.uncompressedSourceCache, + "The uncompressed source code cache."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/script-data"), + KIND_HEAP, rtStats.runtime.scriptData, + "The table holding script data shared in the runtime."); + + nsCString nonNotablePath = + rtPath + nsPrintfCString("runtime/script-sources/source(scripts=%d, )/", + rtStats.runtime.scriptSourceInfo.numScripts); + + ReportScriptSourceStats(rtStats.runtime.scriptSourceInfo, + nonNotablePath, handleReport, data, rtTotal); + + for (size_t i = 0; i < rtStats.runtime.notableScriptSources.length(); i++) { + const JS::NotableScriptSourceInfo& scriptSourceInfo = + rtStats.runtime.notableScriptSources[i]; + + // Escape / to \ before we put the filename into the memory reporter + // path, because we don't want any forward slashes in the string to + // count as path separators. Consumers of memory reporters (e.g. + // about:memory) will convert them back to / after doing path + // splitting. + nsCString escapedFilename; + if (anonymize) { + escapedFilename.AppendPrintf("", int(i)); + } else { + nsDependentCString filename(scriptSourceInfo.filename_); + escapedFilename.Append(filename); + escapedFilename.ReplaceSubstring("/", "\\"); + } + + nsCString notablePath = rtPath + + nsPrintfCString("runtime/script-sources/source(scripts=%d, %s)/", + scriptSourceInfo.numScripts, escapedFilename.get()); + + ReportScriptSourceStats(scriptSourceInfo, notablePath, + handleReport, data, rtTotal); + } + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/ion"), + KIND_NONHEAP, rtStats.runtime.code.ion, + "Code generated by the IonMonkey JIT."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/baseline"), + KIND_NONHEAP, rtStats.runtime.code.baseline, + "Code generated by the Baseline JIT."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/regexp"), + KIND_NONHEAP, rtStats.runtime.code.regexp, + "Code generated by the regexp JIT."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/other"), + KIND_NONHEAP, rtStats.runtime.code.other, + "Code generated by the JITs for wrappers and trampolines."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/code/unused"), + KIND_NONHEAP, rtStats.runtime.code.unused, + "Memory allocated by one of the JITs to hold code, but which is " + "currently unused."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/marker"), + KIND_HEAP, rtStats.runtime.gc.marker, + "The GC mark stack and gray roots."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-committed"), + KIND_NONHEAP, rtStats.runtime.gc.nurseryCommitted, + "Memory being used by the GC's nursery."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-malloced-buffers"), + KIND_HEAP, rtStats.runtime.gc.nurseryMallocedBuffers, + "Out-of-line slots and elements belonging to objects in the nursery."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/vals"), + KIND_HEAP, rtStats.runtime.gc.storeBufferVals, + "Values in the store buffer."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/cells"), + KIND_HEAP, rtStats.runtime.gc.storeBufferCells, + "Cells in the store buffer."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/slots"), + KIND_HEAP, rtStats.runtime.gc.storeBufferSlots, + "Slots in the store buffer."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/whole-cells"), + KIND_HEAP, rtStats.runtime.gc.storeBufferWholeCells, + "Whole cells in the store buffer."); + + RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/generics"), + KIND_HEAP, rtStats.runtime.gc.storeBufferGenerics, + "Generic things in the store buffer."); + + if (rtTotalOut) + *rtTotalOut = rtTotal; + + // Report GC numbers that don't belong to a compartment. + + // We don't want to report decommitted memory in "explicit", so we just + // change the leading "explicit/" to "decommitted/". + nsCString rtPath2(rtPath); + rtPath2.Replace(0, strlen("explicit"), NS_LITERAL_CSTRING("decommitted")); + REPORT_GC_BYTES(rtPath2 + NS_LITERAL_CSTRING("gc-heap/decommitted-arenas"), + rtStats.gcHeapDecommittedArenas, + "GC arenas in non-empty chunks that is decommitted, i.e. it takes up " + "address space but no physical memory or swap space."); + + REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-chunks"), + rtStats.gcHeapUnusedChunks, + "Empty GC chunks which will soon be released unless claimed for new " + "allocations."); + + REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/unused-arenas"), + rtStats.gcHeapUnusedArenas, + "Empty GC arenas within non-empty chunks."); + + REPORT_GC_BYTES(rtPath + NS_LITERAL_CSTRING("gc-heap/chunk-admin"), + rtStats.gcHeapChunkAdmin, + "Bookkeeping information within GC chunks."); + + // gcTotal is the sum of everything we've reported for the GC heap. It + // should equal rtStats.gcHeapChunkTotal. + MOZ_ASSERT(gcTotal == rtStats.gcHeapChunkTotal); +} + +void +ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, + const nsACString& rtPath, + nsIHandleReportCallback* handleReport, + nsISupports* data, + bool anonymize, + size_t* rtTotalOut) +{ + nsCOMPtr am; + if (XRE_IsParentProcess()) { + // Only try to access the service from the main process. + am = do_GetService("@mozilla.org/addons/integration;1"); + } + ReportJSRuntimeExplicitTreeStats(rtStats, rtPath, am.get(), handleReport, + data, anonymize, rtTotalOut); +} + + +} // namespace xpc + +class JSMainRuntimeCompartmentsReporter final : public nsIMemoryReporter +{ + + ~JSMainRuntimeCompartmentsReporter() {} + + public: + NS_DECL_ISUPPORTS + + struct Data { + int anonymizeID; + js::Vector paths; + }; + + static void CompartmentCallback(JSContext* cx, void* vdata, JSCompartment* c) { + // silently ignore OOM errors + Data* data = static_cast(vdata); + nsCString path; + GetCompartmentName(c, path, &data->anonymizeID, /* replaceSlashes = */ true); + path.Insert(js::IsSystemCompartment(c) + ? NS_LITERAL_CSTRING("js-main-runtime-compartments/system/") + : NS_LITERAL_CSTRING("js-main-runtime-compartments/user/"), + 0); + mozilla::Unused << data->paths.append(path); + } + + NS_IMETHOD CollectReports(nsIHandleReportCallback* handleReport, + nsISupports* data, bool anonymize) override + { + // First we collect the compartment paths. Then we report them. Doing + // the two steps interleaved is a bad idea, because calling + // |handleReport| from within CompartmentCallback() leads to all manner + // of assertions. + + Data d; + d.anonymizeID = anonymize ? 1 : 0; + JS_IterateCompartments(nsXPConnect::GetContextInstance()->Context(), + &d, CompartmentCallback); + + for (size_t i = 0; i < d.paths.length(); i++) + REPORT(nsCString(d.paths[i]), KIND_OTHER, UNITS_COUNT, 1, + "A live compartment in the main JSRuntime."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(JSMainRuntimeCompartmentsReporter, nsIMemoryReporter) + +MOZ_DEFINE_MALLOC_SIZE_OF(OrphanMallocSizeOf) + +namespace xpc { + +static size_t +SizeOfTreeIncludingThis(nsINode* tree) +{ + size_t n = tree->SizeOfIncludingThis(OrphanMallocSizeOf); + for (nsIContent* child = tree->GetFirstChild(); child; child = child->GetNextNode(tree)) + n += child->SizeOfIncludingThis(OrphanMallocSizeOf); + + return n; +} + +class OrphanReporter : public JS::ObjectPrivateVisitor +{ + public: + explicit OrphanReporter(GetISupportsFun aGetISupports) + : JS::ObjectPrivateVisitor(aGetISupports) + { + } + + virtual size_t sizeOfIncludingThis(nsISupports* aSupports) override { + size_t n = 0; + nsCOMPtr node = do_QueryInterface(aSupports); + // https://bugzilla.mozilla.org/show_bug.cgi?id=773533#c11 explains + // that we have to skip XBL elements because they violate certain + // assumptions. Yuk. + if (node && !node->IsInUncomposedDoc() && + !(node->IsElement() && node->AsElement()->IsInNamespace(kNameSpaceID_XBL))) + { + // This is an orphan node. If we haven't already handled the + // sub-tree that this node belongs to, measure the sub-tree's size + // and then record its root so we don't measure it again. + nsCOMPtr orphanTree = node->SubtreeRoot(); + if (orphanTree && + !mAlreadyMeasuredOrphanTrees.Contains(orphanTree)) { + // If PutEntry() fails we don't measure this tree, which could + // lead to under-measurement. But that's better than the + // alternatives, which are over-measurement or an OOM abort. + if (mAlreadyMeasuredOrphanTrees.PutEntry(orphanTree, fallible)) { + n += SizeOfTreeIncludingThis(orphanTree); + } + } + } + return n; + } + + private: + nsTHashtable mAlreadyMeasuredOrphanTrees; +}; + +#ifdef DEBUG +static bool +StartsWithExplicit(nsACString& s) +{ + return StringBeginsWith(s, NS_LITERAL_CSTRING("explicit/")); +} +#endif + +class XPCJSContextStats : public JS::RuntimeStats +{ + WindowPaths* mWindowPaths; + WindowPaths* mTopWindowPaths; + bool mGetLocations; + int mAnonymizeID; + + public: + XPCJSContextStats(WindowPaths* windowPaths, WindowPaths* topWindowPaths, + bool getLocations, bool anonymize) + : JS::RuntimeStats(JSMallocSizeOf), + mWindowPaths(windowPaths), + mTopWindowPaths(topWindowPaths), + mGetLocations(getLocations), + mAnonymizeID(anonymize ? 1 : 0) + {} + + ~XPCJSContextStats() { + for (size_t i = 0; i != compartmentStatsVector.length(); ++i) + delete static_cast(compartmentStatsVector[i].extra); + + + for (size_t i = 0; i != zoneStatsVector.length(); ++i) + delete static_cast(zoneStatsVector[i].extra); + } + + virtual void initExtraZoneStats(JS::Zone* zone, JS::ZoneStats* zStats) override { + // Get the compartment's global. + AutoSafeJSContext cx; + JSCompartment* comp = js::GetAnyCompartmentInZone(zone); + xpc::ZoneStatsExtras* extras = new xpc::ZoneStatsExtras; + extras->pathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, comp)); + if (global) { + RefPtr window; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, window))) { + // The global is a |window| object. Use the path prefix that + // we should have already created for it. + if (mTopWindowPaths->Get(window->WindowID(), + &extras->pathPrefix)) + extras->pathPrefix.AppendLiteral("/js-"); + } + } + + extras->pathPrefix += nsPrintfCString("zone(0x%p)/", (void*)zone); + + MOZ_ASSERT(StartsWithExplicit(extras->pathPrefix)); + + zStats->extra = extras; + } + + virtual void initExtraCompartmentStats(JSCompartment* c, + JS::CompartmentStats* cstats) override + { + xpc::CompartmentStatsExtras* extras = new xpc::CompartmentStatsExtras; + nsCString cName; + GetCompartmentName(c, cName, &mAnonymizeID, /* replaceSlashes = */ true); + CompartmentPrivate* cp = CompartmentPrivate::Get(c); + if (cp) { + if (mGetLocations) { + cp->GetLocationURI(CompartmentPrivate::LocationHintAddon, + getter_AddRefs(extras->location)); + } + // Note: cannot use amIAddonManager implementation at this point, + // as it is a JS service and the JS heap is currently not idle. + // Otherwise, we could have computed the add-on id at this point. + } + + // Get the compartment's global. + AutoSafeJSContext cx; + bool needZone = true; + RootedObject global(cx, JS_GetGlobalForCompartmentOrNull(cx, c)); + if (global) { + RefPtr window; + if (NS_SUCCEEDED(UNWRAP_OBJECT(Window, global, window))) { + // The global is a |window| object. Use the path prefix that + // we should have already created for it. + if (mWindowPaths->Get(window->WindowID(), + &extras->jsPathPrefix)) { + extras->domPathPrefix.Assign(extras->jsPathPrefix); + extras->domPathPrefix.AppendLiteral("/dom/"); + extras->jsPathPrefix.AppendLiteral("/js-"); + needZone = false; + } else { + extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + extras->domPathPrefix.AssignLiteral("explicit/dom/unknown-window-global?!/"); + } + } else { + extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + extras->domPathPrefix.AssignLiteral("explicit/dom/non-window-global?!/"); + } + } else { + extras->jsPathPrefix.AssignLiteral("explicit/js-non-window/zones/"); + extras->domPathPrefix.AssignLiteral("explicit/dom/no-global?!/"); + } + + if (needZone) + extras->jsPathPrefix += nsPrintfCString("zone(0x%p)/", (void*)js::GetCompartmentZone(c)); + + extras->jsPathPrefix += NS_LITERAL_CSTRING("compartment(") + cName + NS_LITERAL_CSTRING(")/"); + + // extras->jsPathPrefix is used for almost all the compartment-specific + // reports. At this point it has the form + // "compartment()/". + // + // extras->domPathPrefix is used for DOM orphan nodes, which are + // counted by the JS reporter but reported as part of the DOM + // measurements. At this point it has the form "/dom/" if + // this compartment belongs to an nsGlobalWindow, and + // "explicit/dom/?!/" otherwise (in which case it shouldn't + // be used, because non-nsGlobalWindow compartments shouldn't have + // orphan DOM nodes). + + MOZ_ASSERT(StartsWithExplicit(extras->jsPathPrefix)); + MOZ_ASSERT(StartsWithExplicit(extras->domPathPrefix)); + + cstats->extra = extras; + } +}; + +void +JSReporter::CollectReports(WindowPaths* windowPaths, + WindowPaths* topWindowPaths, + nsIHandleReportCallback* handleReport, + nsISupports* data, + bool anonymize) +{ + XPCJSContext* xpccx = nsXPConnect::GetContextInstance(); + + // In the first step we get all the stats and stash them in a local + // data structure. In the second step we pass all the stashed stats to + // the callback. Separating these steps is important because the + // callback may be a JS function, and executing JS while getting these + // stats seems like a bad idea. + + nsCOMPtr addonManager; + if (XRE_IsParentProcess()) { + // Only try to access the service from the main process. + addonManager = do_GetService("@mozilla.org/addons/integration;1"); + } + bool getLocations = !!addonManager; + XPCJSContextStats rtStats(windowPaths, topWindowPaths, getLocations, + anonymize); + OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); + if (!JS::CollectRuntimeStats(xpccx->Context(), &rtStats, &orphanReporter, + anonymize)) + { + return; + } + + size_t xpcJSRuntimeSize = xpccx->SizeOfIncludingThis(JSMallocSizeOf); + + size_t wrappedJSSize = xpccx->GetMultiCompartmentWrappedJSMap()->SizeOfWrappedJS(JSMallocSizeOf); + + XPCWrappedNativeScope::ScopeSizeInfo sizeInfo(JSMallocSizeOf); + XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(&sizeInfo); + + mozJSComponentLoader* loader = mozJSComponentLoader::Get(); + size_t jsComponentLoaderSize = loader ? loader->SizeOfIncludingThis(JSMallocSizeOf) : 0; + + // This is the second step (see above). First we report stuff in the + // "explicit" tree, then we report other stuff. + + size_t rtTotal = 0; + xpc::ReportJSRuntimeExplicitTreeStats(rtStats, + NS_LITERAL_CSTRING("explicit/js-non-window/"), + addonManager, handleReport, data, + anonymize, &rtTotal); + + // Report the sums of the compartment numbers. + xpc::CompartmentStatsExtras cExtrasTotal; + cExtrasTotal.jsPathPrefix.AssignLiteral("js-main-runtime/compartments/"); + cExtrasTotal.domPathPrefix.AssignLiteral("window-objects/dom/"); + ReportCompartmentStats(rtStats.cTotals, cExtrasTotal, addonManager, + handleReport, data); + + xpc::ZoneStatsExtras zExtrasTotal; + zExtrasTotal.pathPrefix.AssignLiteral("js-main-runtime/zones/"); + ReportZoneStats(rtStats.zTotals, zExtrasTotal, handleReport, data, + anonymize); + + // Report the sum of the runtime/ numbers. + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/runtime"), + KIND_OTHER, rtTotal, + "The sum of all measurements under 'explicit/js-non-window/runtime/'."); + + // Report the numbers for memory outside of compartments. + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-chunks"), + KIND_OTHER, rtStats.gcHeapUnusedChunks, + "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/unused-arenas"), + KIND_OTHER, rtStats.gcHeapUnusedArenas, + "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime/gc-heap/chunk-admin"), + KIND_OTHER, rtStats.gcHeapChunkAdmin, + "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); + + // Report a breakdown of the committed GC space. + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/chunks"), + KIND_OTHER, rtStats.gcHeapUnusedChunks, + "The same as 'explicit/js-non-window/gc-heap/unused-chunks'."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/arenas"), + KIND_OTHER, rtStats.gcHeapUnusedArenas, + "The same as 'explicit/js-non-window/gc-heap/unused-arenas'."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/objects"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.object, + "Unused object cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/strings"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.string, + "Unused string cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/symbols"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.symbol, + "Unused symbol cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/shapes"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.shape, + "Unused shape cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/base-shapes"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.baseShape, + "Unused base shape cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/object-groups"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.objectGroup, + "Unused object group cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/scopes"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.scope, + "Unused scope cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/scripts"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.script, + "Unused script cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/lazy-scripts"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.lazyScript, + "Unused lazy script cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/unused/gc-things/jitcode"), + KIND_OTHER, rtStats.zTotals.unusedGCThings.jitcode, + "Unused jitcode cells within non-empty arenas."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/chunk-admin"), + KIND_OTHER, rtStats.gcHeapChunkAdmin, + "The same as 'explicit/js-non-window/gc-heap/chunk-admin'."); + + REPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/arena-admin"), + KIND_OTHER, rtStats.zTotals.gcHeapArenaAdmin, + "The same as 'js-main-runtime/zones/gc-heap-arena-admin'."); + + size_t gcThingTotal = 0; + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/objects"), + KIND_OTHER, rtStats.cTotals.classInfo.objectsGCHeap, + "Used object cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/strings"), + KIND_OTHER, rtStats.zTotals.stringInfo.sizeOfLiveGCThings(), + "Used string cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/symbols"), + KIND_OTHER, rtStats.zTotals.symbolsGCHeap, + "Used symbol cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/shapes"), + KIND_OTHER, + rtStats.zTotals.shapeInfo.shapesGCHeapTree + rtStats.zTotals.shapeInfo.shapesGCHeapDict, + "Used shape cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/base-shapes"), + KIND_OTHER, rtStats.zTotals.shapeInfo.shapesGCHeapBase, + "Used base shape cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/object-groups"), + KIND_OTHER, rtStats.zTotals.objectGroupsGCHeap, + "Used object group cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/scopes"), + KIND_OTHER, rtStats.zTotals.scopesGCHeap, + "Used scope cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/scripts"), + KIND_OTHER, rtStats.cTotals.scriptsGCHeap, + "Used script cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/lazy-scripts"), + KIND_OTHER, rtStats.zTotals.lazyScriptsGCHeap, + "Used lazy script cells."); + + MREPORT_BYTES(NS_LITERAL_CSTRING("js-main-runtime-gc-heap-committed/used/gc-things/jitcode"), + KIND_OTHER, rtStats.zTotals.jitCodesGCHeap, + "Used jitcode cells."); + + MOZ_ASSERT(gcThingTotal == rtStats.gcHeapGCThings); + + // Report xpconnect. + + REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/runtime"), + KIND_HEAP, xpcJSRuntimeSize, + "The XPConnect runtime."); + + REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/wrappedjs"), + KIND_HEAP, wrappedJSSize, + "Wrappers used to implement XPIDL interfaces with JS."); + + REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/scopes"), + KIND_HEAP, sizeInfo.mScopeAndMapSize, + "XPConnect scopes."); + + REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/proto-iface-cache"), + KIND_HEAP, sizeInfo.mProtoAndIfaceCacheSize, + "Prototype and interface binding caches."); + + REPORT_BYTES(NS_LITERAL_CSTRING("explicit/xpconnect/js-component-loader"), + KIND_HEAP, jsComponentLoaderSize, + "XPConnect's JS component loader."); +} + +static nsresult +JSSizeOfTab(JSObject* objArg, size_t* jsObjectsSize, size_t* jsStringsSize, + size_t* jsPrivateSize, size_t* jsOtherSize) +{ + JSContext* cx = nsXPConnect::GetContextInstance()->Context(); + JS::RootedObject obj(cx, objArg); + + TabSizes sizes; + OrphanReporter orphanReporter(XPCConvert::GetISupportsFromJSObject); + NS_ENSURE_TRUE(JS::AddSizeOfTab(cx, obj, moz_malloc_size_of, + &orphanReporter, &sizes), + NS_ERROR_OUT_OF_MEMORY); + + *jsObjectsSize = sizes.objects; + *jsStringsSize = sizes.strings; + *jsPrivateSize = sizes.private_; + *jsOtherSize = sizes.other; + return NS_OK; +} + +} // namespace xpc + +static void +AccumulateTelemetryCallback(int id, uint32_t sample, const char* key) +{ + switch (id) { + case JS_TELEMETRY_GC_REASON: + Telemetry::Accumulate(Telemetry::GC_REASON_2, sample); + break; + case JS_TELEMETRY_GC_IS_ZONE_GC: + Telemetry::Accumulate(Telemetry::GC_IS_COMPARTMENTAL, sample); + break; + case JS_TELEMETRY_GC_MS: + Telemetry::Accumulate(Telemetry::GC_MS, sample); + break; + case JS_TELEMETRY_GC_BUDGET_MS: + Telemetry::Accumulate(Telemetry::GC_BUDGET_MS, sample); + break; + case JS_TELEMETRY_GC_ANIMATION_MS: + Telemetry::Accumulate(Telemetry::GC_ANIMATION_MS, sample); + break; + case JS_TELEMETRY_GC_MAX_PAUSE_MS: + Telemetry::Accumulate(Telemetry::GC_MAX_PAUSE_MS, sample); + break; + case JS_TELEMETRY_GC_MARK_MS: + Telemetry::Accumulate(Telemetry::GC_MARK_MS, sample); + break; + case JS_TELEMETRY_GC_SWEEP_MS: + Telemetry::Accumulate(Telemetry::GC_SWEEP_MS, sample); + break; + case JS_TELEMETRY_GC_COMPACT_MS: + Telemetry::Accumulate(Telemetry::GC_COMPACT_MS, sample); + break; + case JS_TELEMETRY_GC_MARK_ROOTS_MS: + Telemetry::Accumulate(Telemetry::GC_MARK_ROOTS_MS, sample); + break; + case JS_TELEMETRY_GC_MARK_GRAY_MS: + Telemetry::Accumulate(Telemetry::GC_MARK_GRAY_MS, sample); + break; + case JS_TELEMETRY_GC_SLICE_MS: + Telemetry::Accumulate(Telemetry::GC_SLICE_MS, sample); + break; + case JS_TELEMETRY_GC_SLOW_PHASE: + Telemetry::Accumulate(Telemetry::GC_SLOW_PHASE, sample); + break; + case JS_TELEMETRY_GC_MMU_50: + Telemetry::Accumulate(Telemetry::GC_MMU_50, sample); + break; + case JS_TELEMETRY_GC_RESET: + Telemetry::Accumulate(Telemetry::GC_RESET, sample); + break; + case JS_TELEMETRY_GC_RESET_REASON: + Telemetry::Accumulate(Telemetry::GC_RESET_REASON, sample); + break; + case JS_TELEMETRY_GC_INCREMENTAL_DISABLED: + Telemetry::Accumulate(Telemetry::GC_INCREMENTAL_DISABLED, sample); + break; + case JS_TELEMETRY_GC_NON_INCREMENTAL: + Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL, sample); + break; + case JS_TELEMETRY_GC_NON_INCREMENTAL_REASON: + Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL_REASON, sample); + break; + case JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS: + Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_TOTAL_MS, sample); + break; + case JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS: + Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_MAX_PAUSE_MS, sample); + break; + case JS_TELEMETRY_GC_MINOR_REASON: + Telemetry::Accumulate(Telemetry::GC_MINOR_REASON, sample); + break; + case JS_TELEMETRY_GC_MINOR_REASON_LONG: + Telemetry::Accumulate(Telemetry::GC_MINOR_REASON_LONG, sample); + break; + case JS_TELEMETRY_GC_MINOR_US: + Telemetry::Accumulate(Telemetry::GC_MINOR_US, sample); + break; + case JS_TELEMETRY_GC_NURSERY_BYTES: + Telemetry::Accumulate(Telemetry::GC_NURSERY_BYTES, sample); + break; + case JS_TELEMETRY_GC_PRETENURE_COUNT: + Telemetry::Accumulate(Telemetry::GC_PRETENURE_COUNT, sample); + break; + case JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT: + Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, sample); + break; + case JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS: + Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_ADDONS, sample); + break; + case JS_TELEMETRY_ADDON_EXCEPTIONS: + Telemetry::Accumulate(Telemetry::JS_TELEMETRY_ADDON_EXCEPTIONS, nsDependentCString(key), sample); + break; + case JS_TELEMETRY_AOT_USAGE: + Telemetry::Accumulate(Telemetry::JS_AOT_USAGE, sample); + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected JS_TELEMETRY id"); + } +} + +static void +CompartmentNameCallback(JSContext* cx, JSCompartment* comp, + char* buf, size_t bufsize) +{ + nsCString name; + // This is called via the JSAPI and isn't involved in memory reporting, so + // we don't need to anonymize compartment names. + int anonymizeID = 0; + GetCompartmentName(comp, name, &anonymizeID, /* replaceSlashes = */ false); + if (name.Length() >= bufsize) + name.Truncate(bufsize - 1); + memcpy(buf, name.get(), name.Length() + 1); +} + +static bool +PreserveWrapper(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(cx); + MOZ_ASSERT(obj); + MOZ_ASSERT(IS_WN_REFLECTOR(obj) || mozilla::dom::IsDOMObject(obj)); + + return mozilla::dom::IsDOMObject(obj) && mozilla::dom::TryPreserveWrapper(obj); +} + +static nsresult +ReadSourceFromFilename(JSContext* cx, const char* filename, char16_t** src, size_t* len) +{ + nsresult rv; + + // mozJSSubScriptLoader prefixes the filenames of the scripts it loads with + // the filename of its caller. Axe that if present. + const char* arrow; + while ((arrow = strstr(filename, " -> "))) + filename = arrow + strlen(" -> "); + + // Get the URI. + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), filename); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr scriptChannel; + rv = NS_NewChannel(getter_AddRefs(scriptChannel), + uri, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + + // Only allow local reading. + nsCOMPtr actualUri; + rv = scriptChannel->GetURI(getter_AddRefs(actualUri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCString scheme; + rv = actualUri->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + if (!scheme.EqualsLiteral("file") && !scheme.EqualsLiteral("jar")) + return NS_OK; + + // Explicitly set the content type so that we don't load the + // exthandler to guess it. + scriptChannel->SetContentType(NS_LITERAL_CSTRING("text/plain")); + + nsCOMPtr scriptStream; + rv = scriptChannel->Open2(getter_AddRefs(scriptStream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t rawLen; + rv = scriptStream->Available(&rawLen); + NS_ENSURE_SUCCESS(rv, rv); + if (!rawLen) + return NS_ERROR_FAILURE; + + // Technically, this should be SIZE_MAX, but we don't run on machines + // where that would be less than UINT32_MAX, and the latter is already + // well beyond a reasonable limit. + if (rawLen > UINT32_MAX) + return NS_ERROR_FILE_TOO_BIG; + + // Allocate an internal buf the size of the file. + auto buf = MakeUniqueFallible(rawLen); + if (!buf) + return NS_ERROR_OUT_OF_MEMORY; + + unsigned char* ptr = buf.get(); + unsigned char* end = ptr + rawLen; + while (ptr < end) { + uint32_t bytesRead; + rv = scriptStream->Read(reinterpret_cast(ptr), end - ptr, &bytesRead); + if (NS_FAILED(rv)) + return rv; + MOZ_ASSERT(bytesRead > 0, "stream promised more bytes before EOF"); + ptr += bytesRead; + } + + rv = nsScriptLoader::ConvertToUTF16(scriptChannel, buf.get(), rawLen, EmptyString(), + nullptr, *src, *len); + NS_ENSURE_SUCCESS(rv, rv); + + if (!*src) + return NS_ERROR_FAILURE; + + // Historically this method used JS_malloc() which updates the GC memory + // accounting. Since ConvertToUTF16() now uses js_malloc() instead we + // update the accounting manually after the fact. + JS_updateMallocCounter(cx, *len); + + return NS_OK; +} + +// The JS engine calls this object's 'load' member function when it needs +// the source for a chrome JS function. See the comment in the XPCJSContext +// constructor. +class XPCJSSourceHook: public js::SourceHook { + bool load(JSContext* cx, const char* filename, char16_t** src, size_t* length) { + *src = nullptr; + *length = 0; + + if (!nsContentUtils::IsCallerChrome()) + return true; + + if (!filename) + return true; + + nsresult rv = ReadSourceFromFilename(cx, filename, src, length); + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + return true; + } +}; + +static const JSWrapObjectCallbacks WrapObjectCallbacks = { + xpc::WrapperFactory::Rewrap, + xpc::WrapperFactory::PrepareForWrapping +}; + +XPCJSContext::XPCJSContext() + : mCallContext(nullptr), + mAutoRoots(nullptr), + mResolveName(JSID_VOID), + mResolvingWrapper(nullptr), + mWrappedJSMap(JSObject2WrappedJSMap::newMap(XPC_JS_MAP_LENGTH)), + mWrappedJSClassMap(IID2WrappedJSClassMap::newMap(XPC_JS_CLASS_MAP_LENGTH)), + mIID2NativeInterfaceMap(IID2NativeInterfaceMap::newMap(XPC_NATIVE_INTERFACE_MAP_LENGTH)), + mClassInfo2NativeSetMap(ClassInfo2NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)), + mNativeSetMap(NativeSetMap::newMap(XPC_NATIVE_SET_MAP_LENGTH)), + mThisTranslatorMap(IID2ThisTranslatorMap::newMap(XPC_THIS_TRANSLATOR_MAP_LENGTH)), + mDyingWrappedNativeProtoMap(XPCWrappedNativeProtoMap::newMap(XPC_DYING_NATIVE_PROTO_MAP_LENGTH)), + mGCIsRunning(false), + mNativesToReleaseArray(), + mDoingFinalization(false), + mVariantRoots(nullptr), + mWrappedJSRoots(nullptr), + mObjectHolderRoots(nullptr), + mWatchdogManager(new WatchdogManager(this)), + mAsyncSnowWhiteFreer(new AsyncFreeSnowWhite()), + mSlowScriptSecondHalf(false), + mTimeoutAccumulated(false), + mPendingResult(NS_OK) +{ +} + +#ifdef XP_WIN +static size_t +GetWindowsStackSize() +{ + // First, get the stack base. Because the stack grows down, this is the top + // of the stack. + const uint8_t* stackTop; +#ifdef _WIN64 + PNT_TIB64 pTib = reinterpret_cast(NtCurrentTeb()); + stackTop = reinterpret_cast(pTib->StackBase); +#else + PNT_TIB pTib = reinterpret_cast(NtCurrentTeb()); + stackTop = reinterpret_cast(pTib->StackBase); +#endif + + // Now determine the stack bottom. Note that we can't use tib->StackLimit, + // because that's the size of the committed area and we're also interested + // in the reserved pages below that. + MEMORY_BASIC_INFORMATION mbi; + if (!VirtualQuery(&mbi, &mbi, sizeof(mbi))) + MOZ_CRASH("VirtualQuery failed"); + + const uint8_t* stackBottom = reinterpret_cast(mbi.AllocationBase); + + // Do some sanity checks. + size_t stackSize = size_t(stackTop - stackBottom); + MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024); + MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024); + + // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like + // the guard page and large PGO stack frames. + return stackSize - 10 * sizeof(uintptr_t) * 1024; +} +#endif + +nsresult +XPCJSContext::Initialize() +{ + nsresult rv = CycleCollectedJSContext::Initialize(nullptr, + JS::DefaultHeapMaxBytes, + JS::DefaultNurseryBytes); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + MOZ_ASSERT(Context()); + JSContext* cx = Context(); + + mUnprivilegedJunkScope.init(cx, nullptr); + mPrivilegedJunkScope.init(cx, nullptr); + mCompilationScope.init(cx, nullptr); + + // these jsids filled in later when we have a JSContext to work with. + mStrIDs[0] = JSID_VOID; + + auto cxPrivate = new PerThreadAtomCache(); + memset(cxPrivate, 0, sizeof(PerThreadAtomCache)); + JS_SetContextPrivate(cx, cxPrivate); + + // Unconstrain the runtime's threshold on nominal heap size, to avoid + // triggering GC too often if operating continuously near an arbitrary + // finite threshold (0xffffffff is infinity for uint32_t parameters). + // This leaves the maximum-JS_malloc-bytes threshold still in effect + // to cause period, and we hope hygienic, last-ditch GCs from within + // the GC's allocator. + JS_SetGCParameter(cx, JSGC_MAX_BYTES, 0xffffffff); + + // The JS engine permits us to set different stack limits for system code, + // trusted script, and untrusted script. We have tests that ensure that + // we can always execute 10 "heavy" (eval+with) stack frames deeper in + // privileged code. Our stack sizes vary greatly in different configurations, + // so satisfying those tests requires some care. Manual measurements of the + // number of heavy stack frames achievable gives us the following rough data, + // ordered by the effective categories in which they are grouped in the + // JS_SetNativeStackQuota call (which predates this analysis). + // + // (NB: These numbers may have drifted recently - see bug 938429) + // OSX 64-bit Debug: 7MB stack, 636 stack frames => ~11.3k per stack frame + // OSX64 Opt: 7MB stack, 2440 stack frames => ~3k per stack frame + // + // Linux 32-bit Debug: 2MB stack, 426 stack frames => ~4.8k per stack frame + // Linux 64-bit Debug: 4MB stack, 455 stack frames => ~9.0k per stack frame + // + // Windows (Opt+Debug): 900K stack, 235 stack frames => ~3.4k per stack frame + // + // Linux 32-bit Opt: 1MB stack, 272 stack frames => ~3.8k per stack frame + // Linux 64-bit Opt: 2MB stack, 316 stack frames => ~6.5k per stack frame + // + // We tune the trusted/untrusted quotas for each configuration to achieve our + // invariants while attempting to minimize overhead. In contrast, our buffer + // between system code and trusted script is a very unscientific 10k. + const size_t kSystemCodeBuffer = 10 * 1024; + + // Our "default" stack is what we use in configurations where we don't have + // a compelling reason to do things differently. This is effectively 512KB + // on 32-bit platforms and 1MB on 64-bit platforms. + const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024; + + // Set stack sizes for different configurations. It's probably not great for + // the web to base this decision primarily on the default stack size that the + // underlying platform makes available, but that seems to be what we do. :-( + +#if defined(XP_MACOSX) || defined(DARWIN) + // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB, + // and give trusted script 180k extra. The stack is huge on mac anyway. + const size_t kStackQuota = 7 * 1024 * 1024; + const size_t kTrustedScriptBuffer = 180 * 1024; +#elif defined(MOZ_ASAN) + // ASan requires more stack space due to red-zones, so give it double the + // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements + // were not taken at the time of this writing, so we hazard a guess that + // ASAN builds have roughly thrice the stack overhead as normal builds. + // On normal builds, the largest stack frame size we might encounter is + // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k. + const size_t kStackQuota = 2 * kDefaultStackQuota; + const size_t kTrustedScriptBuffer = 450 * 1024; +#elif defined(XP_WIN) + // 1MB is the default stack size on Windows. We use the /STACK linker flag + // to request a larger stack, so we determine the stack size at runtime. + const size_t kStackQuota = GetWindowsStackSize(); + const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024 //win64 + : 120 * 1024; //win32 + // The following two configurations are linux-only. Given the numbers above, + // we use 50k and 100k trusted buffers on 32-bit and 64-bit respectively. +#elif defined(ANDROID) + // Android appears to have 1MB stacks. Allow the use of 3/4 of that size + // (768KB on 32-bit), since otherwise we can crash with a stack overflow + // when nearing the 1MB limit. + const size_t kStackQuota = kDefaultStackQuota + kDefaultStackQuota / 2; + const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; +#elif defined(DEBUG) + // Bug 803182: account for the 4x difference in the size of js::Interpret + // between optimized and debug builds. + // XXXbholley - Then why do we only account for 2x of difference? + const size_t kStackQuota = 2 * kDefaultStackQuota; + const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; +#else + const size_t kStackQuota = kDefaultStackQuota; + const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800; +#endif + + // Avoid an unused variable warning on platforms where we don't use the + // default. + (void) kDefaultStackQuota; + + JS_SetNativeStackQuota(cx, + kStackQuota, + kStackQuota - kSystemCodeBuffer, + kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer); + + JS_SetDestroyCompartmentCallback(cx, CompartmentDestroyedCallback); + JS_SetSizeOfIncludingThisCompartmentCallback(cx, CompartmentSizeOfIncludingThisCallback); + JS_SetCompartmentNameCallback(cx, CompartmentNameCallback); + mPrevGCSliceCallback = JS::SetGCSliceCallback(cx, GCSliceCallback); + mPrevDoCycleCollectionCallback = JS::SetDoCycleCollectionCallback(cx, + DoCycleCollectionCallback); + JS_AddFinalizeCallback(cx, FinalizeCallback, nullptr); + JS_AddWeakPointerZoneGroupCallback(cx, WeakPointerZoneGroupCallback, this); + JS_AddWeakPointerCompartmentCallback(cx, WeakPointerCompartmentCallback, this); + JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks); + js::SetPreserveWrapperCallback(cx, PreserveWrapper); +#ifdef MOZ_ENABLE_PROFILER_SPS + if (PseudoStack* stack = mozilla_get_pseudo_stack()) + stack->sampleContext(cx); +#endif + JS_SetAccumulateTelemetryCallback(cx, AccumulateTelemetryCallback); + js::SetActivityCallback(cx, ActivityCallback, this); + JS_AddInterruptCallback(cx, InterruptCallback); + js::SetWindowProxyClass(cx, &OuterWindowProxyClass); + + // The JS engine needs to keep the source code around in order to implement + // Function.prototype.toSource(). It'd be nice to not have to do this for + // chrome code and simply stub out requests for source on it. Life is not so + // easy, unfortunately. Nobody relies on chrome toSource() working in core + // browser code, but chrome tests use it. The worst offenders are addons, + // which like to monkeypatch chrome functions by calling toSource() on them + // and using regular expressions to modify them. We avoid keeping most browser + // JS source code in memory by setting LAZY_SOURCE on JS::CompileOptions when + // compiling some chrome code. This causes the JS engine not save the source + // code in memory. When the JS engine is asked to provide the source for a + // function compiled with LAZY_SOURCE, it calls SourceHook to load it. + /// + // Note we do have to retain the source code in memory for scripts compiled in + // isRunOnce mode and compiled function bodies (from + // JS::CompileFunction). In practice, this means content scripts and event + // handlers. + UniquePtr hook(new XPCJSSourceHook); + js::SetSourceHook(cx, Move(hook)); + + // Set up locale information and callbacks for the newly-created context so + // that the various toLocaleString() methods, localeCompare(), and other + // internationalization APIs work as desired. + if (!xpc_LocalizeContext(cx)) + NS_RUNTIMEABORT("xpc_LocalizeContext failed."); + + // Register memory reporters and distinguished amount functions. + RegisterStrongMemoryReporter(new JSMainRuntimeCompartmentsReporter()); + RegisterStrongMemoryReporter(new JSMainRuntimeTemporaryPeakReporter()); + RegisterJSMainRuntimeGCHeapDistinguishedAmount(JSMainRuntimeGCHeapDistinguishedAmount); + RegisterJSMainRuntimeTemporaryPeakDistinguishedAmount(JSMainRuntimeTemporaryPeakDistinguishedAmount); + RegisterJSMainRuntimeCompartmentsSystemDistinguishedAmount(JSMainRuntimeCompartmentsSystemDistinguishedAmount); + RegisterJSMainRuntimeCompartmentsUserDistinguishedAmount(JSMainRuntimeCompartmentsUserDistinguishedAmount); + mozilla::RegisterJSSizeOfTab(JSSizeOfTab); + + // Watch for the JS boolean options. + ReloadPrefsCallback(nullptr, this); + Preferences::RegisterCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR, this); + + return NS_OK; +} + +// static +XPCJSContext* +XPCJSContext::newXPCJSContext() +{ + XPCJSContext* self = new XPCJSContext(); + nsresult rv = self->Initialize(); + if (NS_FAILED(rv)) { + NS_RUNTIMEABORT("new XPCJSContext failed to initialize."); + delete self; + return nullptr; + } + + if (self->Context() && + self->GetMultiCompartmentWrappedJSMap() && + self->GetWrappedJSClassMap() && + self->GetIID2NativeInterfaceMap() && + self->GetClassInfo2NativeSetMap() && + self->GetNativeSetMap() && + self->GetThisTranslatorMap() && + self->GetDyingWrappedNativeProtoMap() && + self->mWatchdogManager) { + return self; + } + + NS_RUNTIMEABORT("new XPCJSContext failed to initialize."); + + delete self; + return nullptr; +} + +bool +XPCJSContext::JSContextInitialized(JSContext* cx) +{ + JSAutoRequest ar(cx); + + // if it is our first context then we need to generate our string ids + if (JSID_IS_VOID(mStrIDs[0])) { + RootedString str(cx); + for (unsigned i = 0; i < IDX_TOTAL_COUNT; i++) { + str = JS_AtomizeAndPinString(cx, mStrings[i]); + if (!str) { + mStrIDs[0] = JSID_VOID; + return false; + } + mStrIDs[i] = INTERNED_STRING_TO_JSID(cx, str); + mStrJSVals[i].setString(str); + } + + if (!mozilla::dom::DefineStaticJSVals(cx)) { + return false; + } + } + + return true; +} + +bool +XPCJSContext::DescribeCustomObjects(JSObject* obj, const js::Class* clasp, + char (&name)[72]) const +{ + XPCNativeScriptableInfo* si = nullptr; + + if (!IS_PROTO_CLASS(clasp)) { + return false; + } + + XPCWrappedNativeProto* p = + static_cast(xpc_GetJSPrivate(obj)); + si = p->GetScriptableInfo(); + + if (!si) { + return false; + } + + SprintfLiteral(name, "JS Object (%s - %s)", clasp->name, si->GetJSClass()->name); + return true; +} + +bool +XPCJSContext::NoteCustomGCThingXPCOMChildren(const js::Class* clasp, JSObject* obj, + nsCycleCollectionTraversalCallback& cb) const +{ + if (clasp != &XPC_WN_Tearoff_JSClass) { + return false; + } + + // A tearoff holds a strong reference to its native object + // (see XPCWrappedNative::FlatJSObjectFinalized). Its XPCWrappedNative + // will be held alive through the parent of the JSObject of the tearoff. + XPCWrappedNativeTearOff* to = + static_cast(xpc_GetJSPrivate(obj)); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "xpc_GetJSPrivate(obj)->mNative"); + cb.NoteXPCOMChild(to->GetNative()); + return true; +} + +void +XPCJSContext::BeforeProcessTask(bool aMightBlock) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If ProcessNextEvent was called during a Promise "then" callback, we + // must process any pending microtasks before blocking in the event loop, + // otherwise we may deadlock until an event enters the queue later. + if (aMightBlock) { + if (Promise::PerformMicroTaskCheckpoint()) { + // If any microtask was processed, we post a dummy event in order to + // force the ProcessNextEvent call not to block. This is required + // to support nested event loops implemented using a pattern like + // "while (condition) thread.processNextEvent(true)", in case the + // condition is triggered here by a Promise "then" callback. + + NS_DispatchToMainThread(new Runnable()); + } + } + + // Start the slow script timer. + mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes(); + mSlowScriptSecondHalf = false; + mSlowScriptActualWait = mozilla::TimeDuration(); + mTimeoutAccumulated = false; + + // As we may be entering a nested event loop, we need to + // cancel any ongoing performance measurement. + js::ResetPerformanceMonitoring(Get()->Context()); + + CycleCollectedJSContext::BeforeProcessTask(aMightBlock); +} + +void +XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth) +{ + // Now that we're back to the event loop, reset the slow script checkpoint. + mSlowScriptCheckpoint = mozilla::TimeStamp(); + mSlowScriptSecondHalf = false; + + // Call cycle collector occasionally. + MOZ_ASSERT(NS_IsMainThread()); + nsJSContext::MaybePokeCC(); + + CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth); + + // Now that we are certain that the event is complete, + // we can flush any ongoing performance measurement. + js::FlushPerformanceMonitoring(Get()->Context()); + + mozilla::jsipc::AfterProcessTask(); +} + +/***************************************************************************/ + +void +XPCJSContext::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCJSContext @ %x", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("mJSContext @ %x", Context())); + + XPC_LOG_ALWAYS(("mWrappedJSClassMap @ %x with %d wrapperclasses(s)", + mWrappedJSClassMap, mWrappedJSClassMap->Count())); + // iterate wrappersclasses... + if (depth && mWrappedJSClassMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mWrappedJSClassMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + entry->value->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + + // iterate wrappers... + XPC_LOG_ALWAYS(("mWrappedJSMap @ %x with %d wrappers(s)", + mWrappedJSMap, mWrappedJSMap->Count())); + if (depth && mWrappedJSMap->Count()) { + XPC_LOG_INDENT(); + mWrappedJSMap->Dump(depth); + XPC_LOG_OUTDENT(); + } + + XPC_LOG_ALWAYS(("mIID2NativeInterfaceMap @ %x with %d interface(s)", + mIID2NativeInterfaceMap, + mIID2NativeInterfaceMap->Count())); + + XPC_LOG_ALWAYS(("mClassInfo2NativeSetMap @ %x with %d sets(s)", + mClassInfo2NativeSetMap, + mClassInfo2NativeSetMap->Count())); + + XPC_LOG_ALWAYS(("mThisTranslatorMap @ %x with %d translator(s)", + mThisTranslatorMap, mThisTranslatorMap->Count())); + + XPC_LOG_ALWAYS(("mNativeSetMap @ %x with %d sets(s)", + mNativeSetMap, mNativeSetMap->Count())); + + // iterate sets... + if (depth && mNativeSetMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mNativeSetMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + entry->key_value->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + + XPC_LOG_ALWAYS(("mPendingResult of %x", mPendingResult)); + + XPC_LOG_OUTDENT(); +#endif +} + +/***************************************************************************/ + +void +XPCRootSetElem::AddToRootSet(XPCRootSetElem** listHead) +{ + MOZ_ASSERT(!mSelfp, "Must be not linked"); + + mSelfp = listHead; + mNext = *listHead; + if (mNext) { + MOZ_ASSERT(mNext->mSelfp == listHead, "Must be list start"); + mNext->mSelfp = &mNext; + } + *listHead = this; +} + +void +XPCRootSetElem::RemoveFromRootSet() +{ + nsXPConnect* xpc = nsXPConnect::XPConnect(); + JS::PokeGC(xpc->GetContext()->Context()); + + MOZ_ASSERT(mSelfp, "Must be linked"); + + MOZ_ASSERT(*mSelfp == this, "Link invariant"); + *mSelfp = mNext; + if (mNext) + mNext->mSelfp = mSelfp; +#ifdef DEBUG + mSelfp = nullptr; + mNext = nullptr; +#endif +} + +void +XPCJSContext::AddGCCallback(xpcGCCallback cb) +{ + MOZ_ASSERT(cb, "null callback"); + extraGCCallbacks.AppendElement(cb); +} + +void +XPCJSContext::RemoveGCCallback(xpcGCCallback cb) +{ + MOZ_ASSERT(cb, "null callback"); + bool found = extraGCCallbacks.RemoveElement(cb); + if (!found) { + NS_ERROR("Removing a callback which was never added."); + } +} + +void +XPCJSContext::InitSingletonScopes() +{ + // This all happens very early, so we don't bother with cx pushing. + JSContext* cx = Context(); + JSAutoRequest ar(cx); + RootedValue v(cx); + nsresult rv; + + // Create the Unprivileged Junk Scope. + SandboxOptions unprivilegedJunkScopeOptions; + unprivilegedJunkScopeOptions.sandboxName.AssignLiteral("XPConnect Junk Compartment"); + unprivilegedJunkScopeOptions.invisibleToDebugger = true; + rv = CreateSandboxObject(cx, &v, nullptr, unprivilegedJunkScopeOptions); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + mUnprivilegedJunkScope = js::UncheckedUnwrap(&v.toObject()); + + // Create the Privileged Junk Scope. + SandboxOptions privilegedJunkScopeOptions; + privilegedJunkScopeOptions.sandboxName.AssignLiteral("XPConnect Privileged Junk Compartment"); + privilegedJunkScopeOptions.invisibleToDebugger = true; + privilegedJunkScopeOptions.wantComponents = false; + rv = CreateSandboxObject(cx, &v, nsXPConnect::SystemPrincipal(), privilegedJunkScopeOptions); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + mPrivilegedJunkScope = js::UncheckedUnwrap(&v.toObject()); + + // Create the Compilation Scope. + SandboxOptions compilationScopeOptions; + compilationScopeOptions.sandboxName.AssignLiteral("XPConnect Compilation Compartment"); + compilationScopeOptions.invisibleToDebugger = true; + compilationScopeOptions.discardSource = ShouldDiscardSystemSource(); + rv = CreateSandboxObject(cx, &v, /* principal = */ nullptr, compilationScopeOptions); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + mCompilationScope = js::UncheckedUnwrap(&v.toObject()); +} + +void +XPCJSContext::DeleteSingletonScopes() +{ + mUnprivilegedJunkScope = nullptr; + mPrivilegedJunkScope = nullptr; + mCompilationScope = nullptr; +} diff --git a/js/xpconnect/src/XPCJSID.cpp b/js/xpconnect/src/XPCJSID.cpp new file mode 100644 index 000000000..b9cbee7be --- /dev/null +++ b/js/xpconnect/src/XPCJSID.cpp @@ -0,0 +1,816 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* An xpcom implementation of the JavaScript nsIID and nsCID objects. */ + +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "mozilla/StaticPtr.h" + +using namespace mozilla::dom; +using namespace JS; + +/***************************************************************************/ +// nsJSID + +NS_IMPL_CLASSINFO(nsJSID, nullptr, 0, NS_JS_ID_CID) +NS_IMPL_ISUPPORTS_CI(nsJSID, nsIJSID) + +const char nsJSID::gNoString[] = ""; + +nsJSID::nsJSID() + : mID(GetInvalidIID()), + mNumber(const_cast(gNoString)), + mName(const_cast(gNoString)) +{ +} + +nsJSID::~nsJSID() +{ + if (mNumber && mNumber != gNoString) + free(mNumber); + if (mName && mName != gNoString) + free(mName); +} + +void nsJSID::Reset() +{ + mID = GetInvalidIID(); + + if (mNumber && mNumber != gNoString) + free(mNumber); + if (mName && mName != gNoString) + free(mName); + + mNumber = mName = nullptr; +} + +bool +nsJSID::SetName(const char* name) +{ + MOZ_ASSERT(!mName || mName == gNoString ,"name already set"); + MOZ_ASSERT(name,"null name"); + mName = NS_strdup(name); + return mName ? true : false; +} + +NS_IMETHODIMP +nsJSID::GetName(char * *aName) +{ + if (!aName) + return NS_ERROR_NULL_POINTER; + + if (!NameIsSet()) + SetNameToNoString(); + MOZ_ASSERT(mName, "name not set"); + *aName = NS_strdup(mName); + return *aName ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsJSID::GetNumber(char * *aNumber) +{ + if (!aNumber) + return NS_ERROR_NULL_POINTER; + + if (!mNumber) { + if (!(mNumber = mID.ToString())) + mNumber = const_cast(gNoString); + } + + *aNumber = NS_strdup(mNumber); + return *aNumber ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP_(const nsID*) +nsJSID::GetID() +{ + return &mID; +} + +NS_IMETHODIMP +nsJSID::GetValid(bool* aValid) +{ + if (!aValid) + return NS_ERROR_NULL_POINTER; + + *aValid = IsValid(); + return NS_OK; +} + +NS_IMETHODIMP +nsJSID::Equals(nsIJSID* other, bool* _retval) +{ + if (!_retval) + return NS_ERROR_NULL_POINTER; + + if (!other || mID.Equals(GetInvalidIID())) { + *_retval = false; + return NS_OK; + } + + *_retval = other->GetID()->Equals(mID); + return NS_OK; +} + +NS_IMETHODIMP +nsJSID::Initialize(const char* idString) +{ + if (!idString) + return NS_ERROR_NULL_POINTER; + + if (*idString != '\0' && mID.Equals(GetInvalidIID())) { + Reset(); + + if (idString[0] == '{') { + if (mID.Parse(idString)) { + return NS_OK; + } + + // error - reset to invalid state + mID = GetInvalidIID(); + } + } + return NS_ERROR_FAILURE; +} + +bool +nsJSID::InitWithName(const nsID& id, const char* nameString) +{ + MOZ_ASSERT(nameString, "no name"); + Reset(); + mID = id; + return SetName(nameString); +} + +// try to use the name, if no name, then use the number +NS_IMETHODIMP +nsJSID::ToString(char** _retval) +{ + if (mName && mName != gNoString) + return GetName(_retval); + + return GetNumber(_retval); +} + +const nsID& +nsJSID::GetInvalidIID() const +{ + // {BB1F47B0-D137-11d2-9841-006008962422} + static const nsID invalid = {0xbb1f47b0, 0xd137, 0x11d2, + {0x98, 0x41, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22}}; + return invalid; +} + +//static +already_AddRefed +nsJSID::NewID(const char* str) +{ + if (!str) { + NS_ERROR("no string"); + return nullptr; + } + + RefPtr idObj = new nsJSID(); + NS_ENSURE_SUCCESS(idObj->Initialize(str), nullptr); + return idObj.forget(); +} + +//static +already_AddRefed +nsJSID::NewID(const nsID& id) +{ + RefPtr idObj = new nsJSID(); + idObj->mID = id; + idObj->mName = nullptr; + idObj->mNumber = nullptr; + return idObj.forget(); +} + + +/***************************************************************************/ +// Class object support so that we can share prototypes of wrapper + +// This class exists just so we can have a shared scriptable helper for +// the nsJSIID class. The instances implement their own helpers. But we +// needed to be able to indicate to the shared prototypes this single flag: +// nsIXPCScriptable::DONT_ENUM_STATIC_PROPS. And having a class to do it is +// the only means we have. Setting this flag on any given instance scriptable +// helper is not sufficient to convey the information that we don't want +// static properties enumerated on the shared proto. + +class SharedScriptableHelperForJSIID final : public nsIXPCScriptable +{ + ~SharedScriptableHelperForJSIID() {} +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCSCRIPTABLE + SharedScriptableHelperForJSIID() {} +}; + +NS_INTERFACE_MAP_BEGIN(SharedScriptableHelperForJSIID) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCScriptable) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(SharedScriptableHelperForJSIID) +NS_IMPL_RELEASE(SharedScriptableHelperForJSIID) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME SharedScriptableHelperForJSIID +#define XPC_MAP_QUOTED_CLASSNAME "JSIID" +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + +static mozilla::StaticRefPtr gSharedScriptableHelperForJSIID; +static bool gClassObjectsWereInited = false; + +static void EnsureClassObjectsInitialized() +{ + if (!gClassObjectsWereInited) { + gSharedScriptableHelperForJSIID = new SharedScriptableHelperForJSIID(); + + gClassObjectsWereInited = true; + } +} + +static nsresult GetSharedScriptableHelperForJSIID(nsIXPCScriptable** helper) +{ + EnsureClassObjectsInitialized(); + nsCOMPtr temp = gSharedScriptableHelperForJSIID.get(); + temp.forget(helper); + return NS_OK; +} + +/******************************************************/ + +#define NULL_CID \ +{ 0x00000000, 0x0000, 0x0000, \ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } + +// We pass nsIClassInfo::DOM_OBJECT so that nsJSIID instances may be created +// in unprivileged scopes. +NS_DECL_CI_INTERFACE_GETTER(nsJSIID) +NS_IMPL_CLASSINFO(nsJSIID, GetSharedScriptableHelperForJSIID, + nsIClassInfo::DOM_OBJECT, NULL_CID) + +NS_DECL_CI_INTERFACE_GETTER(nsJSCID) +NS_IMPL_CLASSINFO(nsJSCID, nullptr, 0, NULL_CID) + +void xpc_DestroyJSxIDClassObjects() +{ + if (gClassObjectsWereInited) { + NS_IF_RELEASE(NS_CLASSINFO_NAME(nsJSIID)); + NS_IF_RELEASE(NS_CLASSINFO_NAME(nsJSCID)); + gSharedScriptableHelperForJSIID = nullptr; + + gClassObjectsWereInited = false; + } +} + +/***************************************************************************/ + +NS_INTERFACE_MAP_BEGIN(nsJSIID) + NS_INTERFACE_MAP_ENTRY(nsIJSID) + NS_INTERFACE_MAP_ENTRY(nsIJSIID) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSID) + NS_IMPL_QUERY_CLASSINFO(nsJSIID) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsJSIID) +NS_IMPL_RELEASE(nsJSIID) +NS_IMPL_CI_INTERFACE_GETTER(nsJSIID, nsIJSID, nsIJSIID) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsJSIID +#define XPC_MAP_QUOTED_CLASSNAME "nsJSIID" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_ENUMERATE +#define XPC_MAP_WANT_HASINSTANCE +#define XPC_MAP_FLAGS nsIXPCScriptable::ALLOW_PROP_MODS_DURING_RESOLVE +#include "xpc_map_end.h" /* This will #undef the above */ + + +nsJSIID::nsJSIID(nsIInterfaceInfo* aInfo) + : mInfo(aInfo) +{ +} + +nsJSIID::~nsJSIID() {} + +// If mInfo is present we use it and ignore mDetails, else we use mDetails. + +NS_IMETHODIMP nsJSIID::GetName(char * *aName) +{ + return mInfo->GetName(aName); +} + +NS_IMETHODIMP nsJSIID::GetNumber(char * *aNumber) +{ + char str[NSID_LENGTH]; + const nsIID* id; + mInfo->GetIIDShared(&id); + id->ToProvidedString(str); + *aNumber = (char*) nsMemory::Clone(str, NSID_LENGTH); + return *aNumber ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP_(const nsID*) nsJSIID::GetID() +{ + const nsIID* id; + mInfo->GetIIDShared(&id); + return id; +} + +NS_IMETHODIMP nsJSIID::GetValid(bool* aValid) +{ + *aValid = true; + return NS_OK; +} + +NS_IMETHODIMP nsJSIID::Equals(nsIJSID* other, bool* _retval) +{ + if (!_retval) + return NS_ERROR_NULL_POINTER; + + if (!other) { + *_retval = false; + return NS_OK; + } + + mInfo->IsIID(other->GetID(), _retval); + return NS_OK; +} + +NS_IMETHODIMP nsJSIID::Initialize(const char* idString) +{ + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsJSIID::ToString(char** _retval) +{ + return mInfo->GetName(_retval); +} + +// static +already_AddRefed +nsJSIID::NewID(nsIInterfaceInfo* aInfo) +{ + if (!aInfo) { + NS_ERROR("no info"); + return nullptr; + } + + bool canScript; + if (NS_FAILED(aInfo->IsScriptable(&canScript)) || !canScript) + return nullptr; + + RefPtr idObj = new nsJSIID(aInfo); + return idObj.forget(); +} + + +NS_IMETHODIMP +nsJSIID::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext * cx, JSObject * objArg, + jsid idArg, bool* resolvedp, + bool* _retval) +{ + RootedObject obj(cx, objArg); + RootedId id(cx, idArg); + XPCCallContext ccx(cx); + + RefPtr iface = + XPCNativeInterface::GetNewOrUsed(mInfo); + + if (!iface) + return NS_OK; + + XPCNativeMember* member = iface->FindMember(id); + if (member && member->IsConstant()) { + RootedValue val(cx); + if (!member->GetConstantValue(ccx, iface, val.address())) + return NS_ERROR_OUT_OF_MEMORY; + + *resolvedp = true; + *_retval = JS_DefinePropertyById(cx, obj, id, val, + JSPROP_ENUMERATE | JSPROP_READONLY | + JSPROP_PERMANENT | JSPROP_RESOLVING); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsJSIID::Enumerate(nsIXPConnectWrappedNative* wrapper, + JSContext * cx, JSObject * objArg, bool* _retval) +{ + // In this case, let's just eagerly resolve... + + RootedObject obj(cx, objArg); + XPCCallContext ccx(cx); + + RefPtr iface = + XPCNativeInterface::GetNewOrUsed(mInfo); + + if (!iface) + return NS_OK; + + uint16_t count = iface->GetMemberCount(); + for (uint16_t i = 0; i < count; i++) { + XPCNativeMember* member = iface->GetMemberAt(i); + if (member && member->IsConstant() && + !xpc_ForcePropertyResolve(cx, obj, member->GetName())) { + return NS_ERROR_UNEXPECTED; + } + } + return NS_OK; +} + +/* + * HasInstance hooks need to find an appropriate reflector in order to function + * properly. There are two complexities that we need to handle: + * + * 1 - Cross-compartment wrappers. Chrome uses over 100 compartments, all with + * system principal. The success of an instanceof check should not depend + * on which compartment an object comes from. At the same time, we want to + * make sure we don't unwrap important security wrappers. + * CheckedUnwrap does the right thing here. + * + * 2 - Prototype chains. Suppose someone creates a vanilla JS object |a| and + * sets its __proto__ to some WN |b|. If |b instanceof nsIFoo| returns true, + * one would expect |a instanceof nsIFoo| to return true as well, since + * instanceof is transitive up the prototype chain in ECMAScript. Moreover, + * there's chrome code that relies on this. + * + * This static method handles both complexities, returning either an XPCWN, a + * DOM object, or null. The object may well be cross-compartment from |cx|. + */ +static nsresult +FindObjectForHasInstance(JSContext* cx, HandleObject objArg, MutableHandleObject target) +{ + RootedObject obj(cx, objArg), proto(cx); + + while (obj && !IS_WN_REFLECTOR(obj) && + !IsDOMObject(obj) && !mozilla::jsipc::IsCPOW(obj)) + { + if (js::IsWrapper(obj)) { + obj = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + continue; + } + + { + JSAutoCompartment ac(cx, obj); + if (!js::GetObjectProto(cx, obj, &proto)) + return NS_ERROR_FAILURE; + } + + obj = proto; + } + + target.set(obj); + return NS_OK; +} + +nsresult +xpc::HasInstance(JSContext* cx, HandleObject objArg, const nsID* iid, bool* bp) +{ + *bp = false; + + RootedObject obj(cx); + nsresult rv = FindObjectForHasInstance(cx, objArg, &obj); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + if (!obj) + return NS_OK; + + if (mozilla::jsipc::IsCPOW(obj)) + return mozilla::jsipc::InstanceOf(obj, iid, bp); + + nsCOMPtr identity = UnwrapReflectorToISupports(obj); + if (!identity) + return NS_OK; + + nsCOMPtr supp; + identity->QueryInterface(*iid, getter_AddRefs(supp)); + *bp = supp; + + // Our old HasInstance implementation operated by invoking FindTearOff on + // XPCWrappedNatives, and various bits of chrome JS came to depend on + // |instanceof| doing an implicit QI if it succeeds. Do a drive-by QI to + // preserve that behavior. This is just a compatibility hack, so we don't + // really care if it fails. + if (IS_WN_REFLECTOR(obj)) + (void) XPCWrappedNative::Get(obj)->FindTearOff(*iid); + + return NS_OK; +} + +NS_IMETHODIMP +nsJSIID::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject * /* unused */, + HandleValue val, bool* bp, bool* _retval) +{ + *bp = false; + + if (val.isPrimitive()) + return NS_OK; + + // we have a JSObject + RootedObject obj(cx, &val.toObject()); + + const nsIID* iid; + mInfo->GetIIDShared(&iid); + return xpc::HasInstance(cx, obj, iid, bp); +} + +/***************************************************************************/ + +NS_INTERFACE_MAP_BEGIN(nsJSCID) + NS_INTERFACE_MAP_ENTRY(nsIJSID) + NS_INTERFACE_MAP_ENTRY(nsIJSCID) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIJSID) + NS_IMPL_QUERY_CLASSINFO(nsJSCID) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(nsJSCID) +NS_IMPL_RELEASE(nsJSCID) +NS_IMPL_CI_INTERFACE_GETTER(nsJSCID, nsIJSID, nsIJSCID) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME nsJSCID +#define XPC_MAP_QUOTED_CLASSNAME "nsJSCID" +#define XPC_MAP_WANT_CONSTRUCT +#define XPC_MAP_WANT_HASINSTANCE +#define XPC_MAP_FLAGS 0 +#include "xpc_map_end.h" /* This will #undef the above */ + +nsJSCID::nsJSCID() { mDetails = new nsJSID(); } +nsJSCID::~nsJSCID() {} + +NS_IMETHODIMP nsJSCID::GetName(char * *aName) + {ResolveName(); return mDetails->GetName(aName);} + +NS_IMETHODIMP nsJSCID::GetNumber(char * *aNumber) + {return mDetails->GetNumber(aNumber);} + +NS_IMETHODIMP_(const nsID*) nsJSCID::GetID() + {return &mDetails->ID();} + +NS_IMETHODIMP nsJSCID::GetValid(bool* aValid) + {return mDetails->GetValid(aValid);} + +NS_IMETHODIMP nsJSCID::Equals(nsIJSID* other, bool* _retval) + {return mDetails->Equals(other, _retval);} + +NS_IMETHODIMP nsJSCID::Initialize(const char* idString) + {return mDetails->Initialize(idString);} + +NS_IMETHODIMP nsJSCID::ToString(char** _retval) + {ResolveName(); return mDetails->ToString(_retval);} + +void +nsJSCID::ResolveName() +{ + if (!mDetails->NameIsSet()) + mDetails->SetNameToNoString(); +} + +//static +already_AddRefed +nsJSCID::NewID(const char* str) +{ + if (!str) { + NS_ERROR("no string"); + return nullptr; + } + + RefPtr idObj = new nsJSCID(); + if (str[0] == '{') { + NS_ENSURE_SUCCESS(idObj->Initialize(str), nullptr); + } else { + nsCOMPtr registrar; + NS_GetComponentRegistrar(getter_AddRefs(registrar)); + NS_ENSURE_TRUE(registrar, nullptr); + + nsCID* cid; + if (NS_FAILED(registrar->ContractIDToCID(str, &cid))) + return nullptr; + bool success = idObj->mDetails->InitWithName(*cid, str); + free(cid); + if (!success) + return nullptr; + } + return idObj.forget(); +} + +static const nsID* +GetIIDArg(uint32_t argc, const JS::Value& val, JSContext* cx) +{ + const nsID* iid; + + // If an IID was passed in then use it + if (argc) { + JSObject* iidobj; + if (val.isPrimitive() || + !(iidobj = val.toObjectOrNull()) || + !(iid = xpc_JSObjectToID(cx, iidobj))) { + return nullptr; + } + } else + iid = &NS_GET_IID(nsISupports); + + return iid; +} + +NS_IMETHODIMP +nsJSCID::CreateInstance(HandleValue iidval, JSContext* cx, + uint8_t optionalArgc, MutableHandleValue retval) +{ + if (!mDetails->IsValid()) + return NS_ERROR_XPC_BAD_CID; + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, mDetails->ID()))) { + NS_ERROR("how are we not being called from chrome here?"); + return NS_OK; + } + + // If an IID was passed in then use it + const nsID* iid = GetIIDArg(optionalArgc, iidval, cx); + if (!iid) + return NS_ERROR_XPC_BAD_IID; + + nsCOMPtr compMgr; + nsresult rv = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr inst; + rv = compMgr->CreateInstance(mDetails->ID(), nullptr, *iid, getter_AddRefs(inst)); + MOZ_ASSERT(NS_FAILED(rv) || inst, "component manager returned success, but instance is null!"); + + if (NS_FAILED(rv) || !inst) + return NS_ERROR_XPC_CI_RETURNED_FAILURE; + + rv = nsContentUtils::WrapNative(cx, inst, iid, retval); + if (NS_FAILED(rv) || retval.isPrimitive()) + return NS_ERROR_XPC_CANT_CREATE_WN; + return NS_OK; +} + +NS_IMETHODIMP +nsJSCID::GetService(HandleValue iidval, JSContext* cx, uint8_t optionalArgc, + MutableHandleValue retval) +{ + if (!mDetails->IsValid()) + return NS_ERROR_XPC_BAD_CID; + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateInstance(cx, mDetails->ID()))) { + MOZ_ASSERT(JS_IsExceptionPending(cx), + "security manager vetoed GetService without setting exception"); + return NS_OK; + } + + // If an IID was passed in then use it + const nsID* iid = GetIIDArg(optionalArgc, iidval, cx); + if (!iid) + return NS_ERROR_XPC_BAD_IID; + + nsCOMPtr svcMgr; + nsresult rv = NS_GetServiceManager(getter_AddRefs(svcMgr)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr srvc; + rv = svcMgr->GetService(mDetails->ID(), *iid, getter_AddRefs(srvc)); + MOZ_ASSERT(NS_FAILED(rv) || srvc, "service manager returned success, but service is null!"); + if (NS_FAILED(rv) || !srvc) + return NS_ERROR_XPC_GS_RETURNED_FAILURE; + + RootedValue v(cx); + rv = nsContentUtils::WrapNative(cx, srvc, iid, &v); + if (NS_FAILED(rv) || !v.isObject()) + return NS_ERROR_XPC_CANT_CREATE_WN; + + retval.set(v); + return NS_OK; +} + +NS_IMETHODIMP +nsJSCID::Construct(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject* objArg, + const CallArgs& args, bool* _retval) +{ + RootedObject obj(cx, objArg); + XPCJSContext* xpccx = nsXPConnect::GetContextInstance(); + if (!xpccx) + return NS_ERROR_FAILURE; + + // 'push' a call context and call on it + RootedId name(cx, xpccx->GetStringID(XPCJSContext::IDX_CREATE_INSTANCE)); + XPCCallContext ccx(cx, obj, nullptr, name, args.length(), args.array(), + args.rval().address()); + + *_retval = XPCWrappedNative::CallMethod(ccx); + return NS_OK; +} + +NS_IMETHODIMP +nsJSCID::HasInstance(nsIXPConnectWrappedNative* wrapper, + JSContext* cx, JSObject * /* unused */, + HandleValue val, bool* bp, bool* _retval) +{ + *bp = false; + + if (!val.isObject()) + return NS_OK; + + RootedObject obj(cx, &val.toObject()); + + // is this really a native xpcom object with a wrapper? + RootedObject target(cx); + nsresult rv = FindObjectForHasInstance(cx, obj, &target); + if (NS_WARN_IF(NS_FAILED(rv))) + return rv; + + if (!target || !IS_WN_REFLECTOR(target)) + return NS_OK; + + if (XPCWrappedNative* other_wrapper = XPCWrappedNative::Get(target)) { + if (nsIClassInfo* ci = other_wrapper->GetClassInfo()) { + // We consider CID equality to be the thing that matters here. + // This is perhaps debatable. + nsID cid; + if (NS_SUCCEEDED(ci->GetClassIDNoAlloc(&cid))) + *bp = cid.Equals(mDetails->ID()); + } + } + + return NS_OK; +} + +/***************************************************************************/ +// additional utilities... + +JSObject* +xpc_NewIDObject(JSContext* cx, HandleObject jsobj, const nsID& aID) +{ + RootedObject obj(cx); + + nsCOMPtr iid = nsJSID::NewID(aID); + if (iid) { + nsXPConnect* xpc = nsXPConnect::XPConnect(); + if (xpc) { + xpc->WrapNative(cx, jsobj, static_cast(iid), + NS_GET_IID(nsIJSID), obj.address()); + } + } + return obj; +} + +// note: returned pointer is only valid while |obj| remains alive! +const nsID* +xpc_JSObjectToID(JSContext* cx, JSObject* obj) +{ + if (!cx || !obj) + return nullptr; + + // NOTE: this call does NOT addref + XPCWrappedNative* wrapper = nullptr; + obj = js::CheckedUnwrap(obj); + if (obj && IS_WN_REFLECTOR(obj)) + wrapper = XPCWrappedNative::Get(obj); + if (wrapper && + (wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID)) || + wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) || + wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID)))) { + return ((nsIJSID*)wrapper->GetIdentityObject())->GetID(); + } + return nullptr; +} + +bool +xpc_JSObjectIsID(JSContext* cx, JSObject* obj) +{ + MOZ_ASSERT(cx && obj, "bad param"); + // NOTE: this call does NOT addref + XPCWrappedNative* wrapper = nullptr; + obj = js::CheckedUnwrap(obj); + if (obj && IS_WN_REFLECTOR(obj)) + wrapper = XPCWrappedNative::Get(obj); + return wrapper && + (wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSID)) || + wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSIID)) || + wrapper->HasInterfaceNoQI(NS_GET_IID(nsIJSCID))); +} + + diff --git a/js/xpconnect/src/XPCJSMemoryReporter.h b/js/xpconnect/src/XPCJSMemoryReporter.h new file mode 100644 index 000000000..7881ac0f5 --- /dev/null +++ b/js/xpconnect/src/XPCJSMemoryReporter.h @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XPCJSMemoryReporter_h +#define XPCJSMemoryReporter_h + +class nsISupports; +class nsIMemoryReporterCallback; + +namespace xpc { + +// The key is the window ID. +typedef nsDataHashtable WindowPaths; + +// This is very nearly an instance of nsIMemoryReporter, but it's not, +// because it's invoked by nsWindowMemoryReporter in order to get |windowPaths| +// in CollectReports. +class JSReporter +{ +public: + static void CollectReports(WindowPaths* windowPaths, + WindowPaths* topWindowPaths, + nsIMemoryReporterCallback* handleReport, + nsISupports* data, + bool anonymize); +}; + +} // namespace xpc + +#endif diff --git a/js/xpconnect/src/XPCJSWeakReference.cpp b/js/xpconnect/src/XPCJSWeakReference.cpp new file mode 100644 index 000000000..bd7c80bc0 --- /dev/null +++ b/js/xpconnect/src/XPCJSWeakReference.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "XPCJSWeakReference.h" + +#include "nsContentUtils.h" + +using namespace JS; + +xpcJSWeakReference::xpcJSWeakReference() +{ +} + +NS_IMPL_ISUPPORTS(xpcJSWeakReference, xpcIJSWeakReference) + +nsresult xpcJSWeakReference::Init(JSContext* cx, const JS::Value& object) +{ + if (!object.isObject()) + return NS_OK; + + JS::RootedObject obj(cx, &object.toObject()); + + XPCCallContext ccx(cx); + + // See if the object is a wrapped native that supports weak references. + nsCOMPtr supports = xpc::UnwrapReflectorToISupports(obj); + nsCOMPtr supportsWeakRef = + do_QueryInterface(supports); + if (supportsWeakRef) { + supportsWeakRef->GetWeakReference(getter_AddRefs(mReferent)); + if (mReferent) { + return NS_OK; + } + } + // If it's not a wrapped native, or it is a wrapped native that does not + // support weak references, fall back to getting a weak ref to the object. + + // See if object is a wrapped JSObject. + RefPtr wrapped; + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(obj, + NS_GET_IID(nsISupports), + getter_AddRefs(wrapped)); + if (!wrapped) { + NS_ERROR("can't get nsISupportsWeakReference wrapper for obj"); + return rv; + } + + return wrapped->GetWeakReference(getter_AddRefs(mReferent)); +} + +NS_IMETHODIMP +xpcJSWeakReference::Get(JSContext* aCx, MutableHandleValue aRetval) +{ + aRetval.setNull(); + + if (!mReferent) { + return NS_OK; + } + + nsCOMPtr supports = do_QueryReferent(mReferent); + if (!supports) { + return NS_OK; + } + + nsCOMPtr wrappedObj = do_QueryInterface(supports); + if (!wrappedObj) { + // We have a generic XPCOM object that supports weak references here. + // Wrap it and pass it out. + return nsContentUtils::WrapNative(aCx, supports, + &NS_GET_IID(nsISupports), + aRetval); + } + + JS::RootedObject obj(aCx, wrappedObj->GetJSObject()); + if (!obj) { + return NS_OK; + } + + // Most users of XPCWrappedJS don't need to worry about + // re-wrapping because things are implicitly rewrapped by + // xpcconvert. However, because we're doing this directly + // through the native call context, we need to call + // JS_WrapObject(). + if (!JS_WrapObject(aCx, &obj)) { + return NS_ERROR_FAILURE; + } + + aRetval.setObject(*obj); + return NS_OK; +} diff --git a/js/xpconnect/src/XPCJSWeakReference.h b/js/xpconnect/src/XPCJSWeakReference.h new file mode 100644 index 000000000..ba802044f --- /dev/null +++ b/js/xpconnect/src/XPCJSWeakReference.h @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef xpcjsweakreference_h___ +#define xpcjsweakreference_h___ + +#include "xpcIJSWeakReference.h" +#include "nsIWeakReference.h" +#include "mozilla/Attributes.h" + +class xpcJSWeakReference final : public xpcIJSWeakReference +{ + ~xpcJSWeakReference() {} + +public: + xpcJSWeakReference(); + nsresult Init(JSContext* cx, const JS::Value& object); + + NS_DECL_ISUPPORTS + NS_DECL_XPCIJSWEAKREFERENCE + +private: + nsCOMPtr mReferent; +}; + +#endif // xpcjsweakreference_h___ diff --git a/js/xpconnect/src/XPCLocale.cpp b/js/xpconnect/src/XPCLocale.cpp new file mode 100644 index 000000000..2fe78cb75 --- /dev/null +++ b/js/xpconnect/src/XPCLocale.cpp @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Assertions.h" + +#include "jsapi.h" + +#include "nsCollationCID.h" +#include "nsJSUtils.h" +#include "nsIPlatformCharset.h" +#include "nsILocaleService.h" +#include "nsICollation.h" +#include "nsUnicharUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/Preferences.h" +#include "nsIUnicodeDecoder.h" + +#include "xpcpublic.h" + +using namespace JS; +using mozilla::dom::EncodingUtils; + +/** + * JS locale callbacks implemented by XPCOM modules. These are theoretically + * safe for use on multiple threads. Unfortunately, the intl code underlying + * these XPCOM modules doesn't yet support this, so in practice + * XPCLocaleCallbacks are limited to the main thread. + */ +struct XPCLocaleCallbacks : public JSLocaleCallbacks +{ + XPCLocaleCallbacks() +#ifdef DEBUG + : mThread(PR_GetCurrentThread()) +#endif + { + MOZ_COUNT_CTOR(XPCLocaleCallbacks); + + localeToUpperCase = LocaleToUpperCase; + localeToLowerCase = LocaleToLowerCase; + localeCompare = LocaleCompare; + localeToUnicode = LocaleToUnicode; + } + + ~XPCLocaleCallbacks() + { + AssertThreadSafety(); + MOZ_COUNT_DTOR(XPCLocaleCallbacks); + } + + /** + * Return the XPCLocaleCallbacks that's hidden away in |cx|. (This impl uses + * the locale callbacks struct to store away its per-context data.) + */ + static XPCLocaleCallbacks* + This(JSContext* cx) + { + // Locale information for |cx| was associated using xpc_LocalizeContext; + // assert and double-check this. + const JSLocaleCallbacks* lc = JS_GetLocaleCallbacks(cx); + MOZ_ASSERT(lc); + MOZ_ASSERT(lc->localeToUpperCase == LocaleToUpperCase); + MOZ_ASSERT(lc->localeToLowerCase == LocaleToLowerCase); + MOZ_ASSERT(lc->localeCompare == LocaleCompare); + MOZ_ASSERT(lc->localeToUnicode == LocaleToUnicode); + + const XPCLocaleCallbacks* ths = static_cast(lc); + ths->AssertThreadSafety(); + return const_cast(ths); + } + + static bool + LocaleToUpperCase(JSContext* cx, HandleString src, MutableHandleValue rval) + { + return ChangeCase(cx, src, rval, ToUpperCase); + } + + static bool + LocaleToLowerCase(JSContext* cx, HandleString src, MutableHandleValue rval) + { + return ChangeCase(cx, src, rval, ToLowerCase); + } + + static bool + LocaleToUnicode(JSContext* cx, const char* src, MutableHandleValue rval) + { + return This(cx)->ToUnicode(cx, src, rval); + } + + static bool + LocaleCompare(JSContext* cx, HandleString src1, HandleString src2, MutableHandleValue rval) + { + return This(cx)->Compare(cx, src1, src2, rval); + } + +private: + static bool + ChangeCase(JSContext* cx, HandleString src, MutableHandleValue rval, + void(*changeCaseFnc)(const nsAString&, nsAString&)) + { + nsAutoJSString autoStr; + if (!autoStr.init(cx, src)) { + return false; + } + + nsAutoString result; + changeCaseFnc(autoStr, result); + + JSString* ucstr = + JS_NewUCStringCopyN(cx, result.get(), result.Length()); + if (!ucstr) { + return false; + } + + rval.setString(ucstr); + return true; + } + + bool + Compare(JSContext* cx, HandleString src1, HandleString src2, MutableHandleValue rval) + { + nsresult rv; + + if (!mCollation) { + nsCOMPtr localeService = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr locale; + rv = localeService->GetApplicationLocale(getter_AddRefs(locale)); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr colFactory = + do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + rv = colFactory->CreateCollation(locale, getter_AddRefs(mCollation)); + } + } + } + + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + } + + nsAutoJSString autoStr1, autoStr2; + if (!autoStr1.init(cx, src1) || !autoStr2.init(cx, src2)) { + return false; + } + + int32_t result; + rv = mCollation->CompareString(nsICollation::kCollationStrengthDefault, + autoStr1, autoStr2, &result); + + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + rval.setInt32(result); + return true; + } + + bool + ToUnicode(JSContext* cx, const char* src, MutableHandleValue rval) + { + nsresult rv; + + if (!mDecoder) { + // use app default locale + nsCOMPtr localeService = + do_GetService(NS_LOCALESERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr appLocale; + rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale)); + if (NS_SUCCEEDED(rv)) { + nsAutoString localeStr; + rv = appLocale-> + GetCategory(NS_LITERAL_STRING(NSILOCALE_TIME), localeStr); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to get app locale info"); + + nsCOMPtr platformCharset = + do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) { + nsAutoCString charset; + rv = platformCharset->GetDefaultCharsetForLocale(localeStr, charset); + if (NS_SUCCEEDED(rv)) { + mDecoder = EncodingUtils::DecoderForEncoding(charset); + } + } + } + } + } + + int32_t srcLength = strlen(src); + + if (mDecoder) { + int32_t unicharLength = srcLength; + char16_t* unichars = + (char16_t*)JS_malloc(cx, (srcLength + 1) * sizeof(char16_t)); + if (unichars) { + rv = mDecoder->Convert(src, &srcLength, unichars, &unicharLength); + if (NS_SUCCEEDED(rv)) { + // terminate the returned string + unichars[unicharLength] = 0; + + // nsIUnicodeDecoder::Convert may use fewer than srcLength PRUnichars + if (unicharLength + 1 < srcLength + 1) { + char16_t* shrunkUnichars = + (char16_t*)JS_realloc(cx, unichars, + (srcLength + 1) * sizeof(char16_t), + (unicharLength + 1) * sizeof(char16_t)); + if (shrunkUnichars) + unichars = shrunkUnichars; + } + JSString* str = JS_NewUCString(cx, reinterpret_cast(unichars), unicharLength); + if (str) { + rval.setString(str); + return true; + } + } + JS_free(cx, unichars); + } + } + + xpc::Throw(cx, NS_ERROR_OUT_OF_MEMORY); + return false; + } + + void AssertThreadSafety() const + { + MOZ_ASSERT(mThread == PR_GetCurrentThread(), + "XPCLocaleCallbacks used unsafely!"); + } + + nsCOMPtr mCollation; + nsCOMPtr mDecoder; +#ifdef DEBUG + PRThread* mThread; +#endif +}; + +bool +xpc_LocalizeContext(JSContext* cx) +{ + JS_SetLocaleCallbacks(cx, new XPCLocaleCallbacks()); + + // Set the default locale. + + // Check a pref to see if we should use US English locale regardless + // of the system locale. + if (Preferences::GetBool("javascript.use_us_english_locale", false)) { + return JS_SetDefaultLocale(cx, "en-US"); + } + + // No pref has been found, so get the default locale from the + // application's locale. + nsCOMPtr localeService = + do_GetService(NS_LOCALESERVICE_CONTRACTID); + if (!localeService) + return false; + + nsCOMPtr appLocale; + nsresult rv = localeService->GetApplicationLocale(getter_AddRefs(appLocale)); + if (NS_FAILED(rv)) + return false; + + nsAutoString localeStr; + rv = appLocale->GetCategory(NS_LITERAL_STRING(NSILOCALE_TIME), localeStr); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to get app locale info"); + NS_LossyConvertUTF16toASCII locale(localeStr); + + return JS_SetDefaultLocale(cx, locale.get()); +} + +void +xpc_DelocalizeContext(JSContext* cx) +{ + const XPCLocaleCallbacks* lc = XPCLocaleCallbacks::This(cx); + JS_SetLocaleCallbacks(cx, nullptr); + delete lc; +} diff --git a/js/xpconnect/src/XPCLog.cpp b/js/xpconnect/src/XPCLog.cpp new file mode 100644 index 000000000..515af2a47 --- /dev/null +++ b/js/xpconnect/src/XPCLog.cpp @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Debug Logging support. */ + +#include "XPCLog.h" +#include "mozilla/Logging.h" +#include "prprf.h" +#include "mozilla/mozalloc.h" +#include +#include + +// this all only works for DEBUG... +#ifdef DEBUG + +#define SPACE_COUNT 200 +#define LINE_LEN 200 +#define INDENT_FACTOR 2 + +#define CAN_RUN (g_InitState == 1 || (g_InitState == 0 && Init())) + +static char* g_Spaces; +static int g_InitState = 0; +static int g_Indent = 0; +static mozilla::LazyLogModule g_LogMod("xpclog"); + +static bool Init() +{ + g_Spaces = new char[SPACE_COUNT+1]; + if (!g_Spaces || !MOZ_LOG_TEST(g_LogMod,LogLevel::Error)) { + g_InitState = 1; + XPC_Log_Finish(); + return false; + } + memset(g_Spaces, ' ', SPACE_COUNT); + g_Spaces[SPACE_COUNT] = 0; + g_InitState = 1; + return true; +} + +void +XPC_Log_Finish() +{ + if (g_InitState == 1) { + delete [] g_Spaces; + } + g_InitState = -1; +} + +void +XPC_Log_print(const char* fmt, ...) +{ + va_list ap; + char line[LINE_LEN]; + + va_start(ap, fmt); + PR_vsnprintf(line, sizeof(line)-1, fmt, ap); + va_end(ap); + if (g_Indent) + PR_LogPrint("%s%s",g_Spaces+SPACE_COUNT-(INDENT_FACTOR*g_Indent),line); + else + PR_LogPrint("%s",line); +} + +bool +XPC_Log_Check(int i) +{ + return CAN_RUN && MOZ_LOG_TEST(g_LogMod,LogLevel::Error); +} + +void +XPC_Log_Indent() +{ + if (INDENT_FACTOR*(++g_Indent) > SPACE_COUNT) + g_Indent-- ; +} + +void +XPC_Log_Outdent() +{ + if (--g_Indent < 0) + g_Indent++; +} + +void +XPC_Log_Clear_Indent() +{ + g_Indent = 0; +} + +#endif diff --git a/js/xpconnect/src/XPCLog.h b/js/xpconnect/src/XPCLog.h new file mode 100644 index 000000000..e5f5a2cf0 --- /dev/null +++ b/js/xpconnect/src/XPCLog.h @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Debug Logging support. */ + +#ifndef xpclog_h___ +#define xpclog_h___ + +#include "mozilla/Logging.h" + +/* + * This uses mozilla/Logging.h. The module name used here is 'xpclog'. + * These environment settings should work... + * + * SET MOZ_LOG=xpclog:5 + * SET MOZ_LOG_FILE=logfile.txt + * + * usage: + * XPC_LOG_ERROR(("my comment number %d", 5)) // note the double parens + * + */ + +#ifdef DEBUG +#define XPC_LOG_INTERNAL(number,_args) \ + do{if (XPC_Log_Check(number)){XPC_Log_print _args;}}while (0) + +#define XPC_LOG_ALWAYS(_args) XPC_LOG_INTERNAL(1,_args) +#define XPC_LOG_ERROR(_args) XPC_LOG_INTERNAL(2,_args) +#define XPC_LOG_WARNING(_args) XPC_LOG_INTERNAL(3,_args) +#define XPC_LOG_DEBUG(_args) XPC_LOG_INTERNAL(4,_args) +#define XPC_LOG_FLUSH() PR_LogFlush() +#define XPC_LOG_INDENT() XPC_Log_Indent() +#define XPC_LOG_OUTDENT() XPC_Log_Outdent() +#define XPC_LOG_CLEAR_INDENT() XPC_Log_Clear_Indent() +#define XPC_LOG_FINISH() XPC_Log_Finish() + +extern "C" { + +void XPC_Log_print(const char* fmt, ...); +bool XPC_Log_Check(int i); +void XPC_Log_Indent(); +void XPC_Log_Outdent(); +void XPC_Log_Clear_Indent(); +void XPC_Log_Finish(); + +} // extern "C" + +#else + +#define XPC_LOG_ALWAYS(_args) ((void)0) +#define XPC_LOG_ERROR(_args) ((void)0) +#define XPC_LOG_WARNING(_args) ((void)0) +#define XPC_LOG_DEBUG(_args) ((void)0) +#define XPC_LOG_FLUSH() ((void)0) +#define XPC_LOG_INDENT() ((void)0) +#define XPC_LOG_OUTDENT() ((void)0) +#define XPC_LOG_CLEAR_INDENT() ((void)0) +#define XPC_LOG_FINISH() ((void)0) +#endif + +#endif /* xpclog_h___ */ diff --git a/js/xpconnect/src/XPCMaps.cpp b/js/xpconnect/src/XPCMaps.cpp new file mode 100644 index 000000000..0d728d7de --- /dev/null +++ b/js/xpconnect/src/XPCMaps.cpp @@ -0,0 +1,405 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Private maps (hashtables). */ + +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "xpcprivate.h" + +#include "js/HashTable.h" + +using namespace mozilla; + +/***************************************************************************/ +// static shared... + +// Note this is returning the bit pattern of the first part of the nsID, not +// the pointer to the nsID. + +static PLDHashNumber +HashIIDPtrKey(const void* key) +{ + return *((js::HashNumber*)key); +} + +static bool +MatchIIDPtrKey(const PLDHashEntryHdr* entry, const void* key) +{ + return ((const nsID*)key)-> + Equals(*((const nsID*)((PLDHashEntryStub*)entry)->key)); +} + +static PLDHashNumber +HashNativeKey(const void* data) +{ + return static_cast(data)->Hash(); +} + +/***************************************************************************/ +// implement JSObject2WrappedJSMap... + +void +JSObject2WrappedJSMap::UpdateWeakPointersAfterGC(XPCJSContext* context) +{ + // Check all wrappers and update their JSObject pointer if it has been + // moved. Release any wrappers whose weakly held JSObject has died. + + nsTArray> dying; + for (Map::Enum e(mTable); !e.empty(); e.popFront()) { + nsXPCWrappedJS* wrapper = e.front().value(); + MOZ_ASSERT(wrapper, "found a null JS wrapper!"); + + // Walk the wrapper chain and update all JSObjects. + while (wrapper) { +#ifdef DEBUG + if (!wrapper->IsSubjectToFinalization()) { + // If a wrapper is not subject to finalization then it roots its + // JS object. If so, then it will not be about to be finalized + // and any necessary pointer update will have already happened + // when it was marked. + JSObject* obj = wrapper->GetJSObjectPreserveColor(); + JSObject* prior = obj; + JS_UpdateWeakPointerAfterGCUnbarriered(&obj); + MOZ_ASSERT(obj == prior); + } +#endif + if (wrapper->IsSubjectToFinalization()) { + wrapper->UpdateObjectPointerAfterGC(); + if (!wrapper->GetJSObjectPreserveColor()) + dying.AppendElement(dont_AddRef(wrapper)); + } + wrapper = wrapper->GetNextWrapper(); + } + + // Remove or update the JSObject key in the table if necessary. + JSObject* obj = e.front().key().unbarrieredGet(); + JS_UpdateWeakPointerAfterGCUnbarriered(&obj); + if (!obj) + e.removeFront(); + else + e.front().mutableKey() = obj; + } +} + +void +JSObject2WrappedJSMap::ShutdownMarker() +{ + for (Map::Range r = mTable.all(); !r.empty(); r.popFront()) { + nsXPCWrappedJS* wrapper = r.front().value(); + MOZ_ASSERT(wrapper, "found a null JS wrapper!"); + MOZ_ASSERT(wrapper->IsValid(), "found an invalid JS wrapper!"); + wrapper->SystemIsBeingShutDown(); + } +} + +size_t +JSObject2WrappedJSMap::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + n += mTable.sizeOfExcludingThis(mallocSizeOf); + return n; +} + +size_t +JSObject2WrappedJSMap::SizeOfWrappedJS(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = 0; + for (Map::Range r = mTable.all(); !r.empty(); r.popFront()) + n += r.front().value()->SizeOfIncludingThis(mallocSizeOf); + return n; +} + +/***************************************************************************/ +// implement Native2WrappedNativeMap... + +// static +Native2WrappedNativeMap* +Native2WrappedNativeMap::newMap(int length) +{ + return new Native2WrappedNativeMap(length); +} + +Native2WrappedNativeMap::Native2WrappedNativeMap(int length) + : mTable(PLDHashTable::StubOps(), sizeof(Entry), length) +{ +} + +size_t +Native2WrappedNativeMap::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + n += mTable.ShallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + n += mallocSizeOf(entry->value); + } + return n; +} + +/***************************************************************************/ +// implement IID2WrappedJSClassMap... + +const struct PLDHashTableOps IID2WrappedJSClassMap::Entry::sOps = +{ + HashIIDPtrKey, + MatchIIDPtrKey, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub +}; + +// static +IID2WrappedJSClassMap* +IID2WrappedJSClassMap::newMap(int length) +{ + return new IID2WrappedJSClassMap(length); +} + +IID2WrappedJSClassMap::IID2WrappedJSClassMap(int length) + : mTable(&Entry::sOps, sizeof(Entry), length) +{ +} + +/***************************************************************************/ +// implement IID2NativeInterfaceMap... + +const struct PLDHashTableOps IID2NativeInterfaceMap::Entry::sOps = +{ + HashIIDPtrKey, + MatchIIDPtrKey, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub +}; + +// static +IID2NativeInterfaceMap* +IID2NativeInterfaceMap::newMap(int length) +{ + return new IID2NativeInterfaceMap(length); +} + +IID2NativeInterfaceMap::IID2NativeInterfaceMap(int length) + : mTable(&Entry::sOps, sizeof(Entry), length) +{ +} + +size_t +IID2NativeInterfaceMap::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + n += mTable.ShallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + n += entry->value->SizeOfIncludingThis(mallocSizeOf); + } + return n; +} + +/***************************************************************************/ +// implement ClassInfo2NativeSetMap... + +// static +bool ClassInfo2NativeSetMap::Entry::Match(const PLDHashEntryHdr* aEntry, + const void* aKey) +{ + return static_cast(aEntry)->key == aKey; +} + +// static +void ClassInfo2NativeSetMap::Entry::Clear(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) +{ + auto entry = static_cast(aEntry); + NS_RELEASE(entry->value); + + entry->key = nullptr; + entry->value = nullptr; +} + +const PLDHashTableOps ClassInfo2NativeSetMap::Entry::sOps = +{ + PLDHashTable::HashVoidPtrKeyStub, + Match, + PLDHashTable::MoveEntryStub, + Clear, + nullptr +}; + +// static +ClassInfo2NativeSetMap* +ClassInfo2NativeSetMap::newMap(int length) +{ + return new ClassInfo2NativeSetMap(length); +} + +ClassInfo2NativeSetMap::ClassInfo2NativeSetMap(int length) + : mTable(&ClassInfo2NativeSetMap::Entry::sOps, sizeof(Entry), length) +{ +} + +size_t +ClassInfo2NativeSetMap::ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) +{ + size_t n = mallocSizeOf(this); + n += mTable.ShallowSizeOfExcludingThis(mallocSizeOf); + return n; +} + +/***************************************************************************/ +// implement ClassInfo2WrappedNativeProtoMap... + +// static +ClassInfo2WrappedNativeProtoMap* +ClassInfo2WrappedNativeProtoMap::newMap(int length) +{ + return new ClassInfo2WrappedNativeProtoMap(length); +} + +ClassInfo2WrappedNativeProtoMap::ClassInfo2WrappedNativeProtoMap(int length) + : mTable(PLDHashTable::StubOps(), sizeof(Entry), length) +{ +} + +size_t +ClassInfo2WrappedNativeProtoMap::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + n += mTable.ShallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + n += mallocSizeOf(entry->value); + } + return n; +} + +/***************************************************************************/ +// implement NativeSetMap... + +bool +NativeSetMap::Entry::Match(const PLDHashEntryHdr* entry, const void* key) +{ + auto Key = static_cast(key); + XPCNativeSet* SetInTable = ((Entry*)entry)->key_value; + XPCNativeSet* Set = Key->GetBaseSet(); + XPCNativeInterface* Addition = Key->GetAddition(); + + if (!Set) { + // This is a special case to deal with the invariant that says: + // "All sets have exactly one nsISupports interface and it comes first." + // See XPCNativeSet::NewInstance for details. + // + // Though we might have a key that represents only one interface, we + // know that if that one interface were contructed into a set then + // it would end up really being a set with two interfaces (except for + // the case where the one interface happened to be nsISupports). + + return (SetInTable->GetInterfaceCount() == 1 && + SetInTable->GetInterfaceAt(0) == Addition) || + (SetInTable->GetInterfaceCount() == 2 && + SetInTable->GetInterfaceAt(1) == Addition); + } + + if (!Addition && Set == SetInTable) + return true; + + uint16_t count = Set->GetInterfaceCount(); + if (count + (Addition ? 1 : 0) != SetInTable->GetInterfaceCount()) + return false; + + XPCNativeInterface** CurrentInTable = SetInTable->GetInterfaceArray(); + XPCNativeInterface** Current = Set->GetInterfaceArray(); + for (uint16_t i = 0; i < count; i++) { + if (*(Current++) != *(CurrentInTable++)) + return false; + } + return !Addition || Addition == *(CurrentInTable++); +} + +const struct PLDHashTableOps NativeSetMap::Entry::sOps = +{ + HashNativeKey, + Match, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub +}; + +// static +NativeSetMap* +NativeSetMap::newMap(int length) +{ + return new NativeSetMap(length); +} + +NativeSetMap::NativeSetMap(int length) + : mTable(&Entry::sOps, sizeof(Entry), length) +{ +} + +size_t +NativeSetMap::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + size_t n = mallocSizeOf(this); + n += mTable.ShallowSizeOfExcludingThis(mallocSizeOf); + for (auto iter = mTable.ConstIter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + n += entry->key_value->SizeOfIncludingThis(mallocSizeOf); + } + return n; +} + +/***************************************************************************/ +// implement IID2ThisTranslatorMap... + +bool +IID2ThisTranslatorMap::Entry::Match(const PLDHashEntryHdr* entry, + const void* key) +{ + return ((const nsID*)key)->Equals(((Entry*)entry)->key); +} + +void +IID2ThisTranslatorMap::Entry::Clear(PLDHashTable* table, PLDHashEntryHdr* entry) +{ + static_cast(entry)->value = nullptr; + memset(entry, 0, table->EntrySize()); +} + +const struct PLDHashTableOps IID2ThisTranslatorMap::Entry::sOps = +{ + HashIIDPtrKey, + Match, + PLDHashTable::MoveEntryStub, + Clear +}; + +// static +IID2ThisTranslatorMap* +IID2ThisTranslatorMap::newMap(int length) +{ + return new IID2ThisTranslatorMap(length); +} + +IID2ThisTranslatorMap::IID2ThisTranslatorMap(int length) + : mTable(&Entry::sOps, sizeof(Entry), length) +{ +} + +/***************************************************************************/ +// implement XPCWrappedNativeProtoMap... + +// static +XPCWrappedNativeProtoMap* +XPCWrappedNativeProtoMap::newMap(int length) +{ + return new XPCWrappedNativeProtoMap(length); +} + +XPCWrappedNativeProtoMap::XPCWrappedNativeProtoMap(int length) + : mTable(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), length) +{ +} + +/***************************************************************************/ diff --git a/js/xpconnect/src/XPCMaps.h b/js/xpconnect/src/XPCMaps.h new file mode 100644 index 000000000..80c51d477 --- /dev/null +++ b/js/xpconnect/src/XPCMaps.h @@ -0,0 +1,606 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Private maps (hashtables). */ + +#ifndef xpcmaps_h___ +#define xpcmaps_h___ + +#include "mozilla/MemoryReporting.h" + +#include "js/GCHashTable.h" + +// Maps... + +// Note that most of the declarations for hash table entries begin with +// a pointer to something or another. This makes them look enough like +// the PLDHashEntryStub struct that the default ops (PLDHashTable::StubOps()) +// just do the right thing for most of our needs. + +// no virtuals in the maps - all the common stuff inlined +// templates could be used to good effect here. + +/*************************/ + +class JSObject2WrappedJSMap +{ + using Map = js::HashMap, + nsXPCWrappedJS*, + js::MovableCellHasher>, + InfallibleAllocPolicy>; + +public: + static JSObject2WrappedJSMap* newMap(int length) { + auto* map = new JSObject2WrappedJSMap(); + if (!map->mTable.init(length)) { + // This is a decent estimate of the size of the hash table's + // entry storage. The |2| is because on average the capacity is + // twice the requested length. + NS_ABORT_OOM(length * 2 * sizeof(Map::Entry)); + } + return map; + } + + inline nsXPCWrappedJS* Find(JSObject* Obj) { + NS_PRECONDITION(Obj,"bad param"); + Map::Ptr p = mTable.lookup(Obj); + return p ? p->value() : nullptr; + } + +#ifdef DEBUG + inline bool HasWrapper(nsXPCWrappedJS* wrapper) { + for (auto r = mTable.all(); !r.empty(); r.popFront()) { + if (r.front().value() == wrapper) + return true; + } + return false; + } +#endif + + inline nsXPCWrappedJS* Add(JSContext* cx, nsXPCWrappedJS* wrapper) { + NS_PRECONDITION(wrapper,"bad param"); + JSObject* obj = wrapper->GetJSObjectPreserveColor(); + Map::AddPtr p = mTable.lookupForAdd(obj); + if (p) + return p->value(); + if (!mTable.add(p, obj, wrapper)) + return nullptr; + return wrapper; + } + + inline void Remove(nsXPCWrappedJS* wrapper) { + NS_PRECONDITION(wrapper,"bad param"); + mTable.remove(wrapper->GetJSObjectPreserveColor()); + } + + inline uint32_t Count() {return mTable.count();} + + inline void Dump(int16_t depth) { + for (Map::Range r = mTable.all(); !r.empty(); r.popFront()) + r.front().value()->DebugDump(depth); + } + + void UpdateWeakPointersAfterGC(XPCJSContext* context); + + void ShutdownMarker(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + // Report the sum of SizeOfIncludingThis() for all wrapped JS in the map. + // Each wrapped JS is only in one map. + size_t SizeOfWrappedJS(mozilla::MallocSizeOf mallocSizeOf) const; + +private: + JSObject2WrappedJSMap() {} + + Map mTable; +}; + +/*************************/ + +class Native2WrappedNativeMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + nsISupports* key; + XPCWrappedNative* value; + }; + + static Native2WrappedNativeMap* newMap(int length); + + inline XPCWrappedNative* Find(nsISupports* Obj) + { + NS_PRECONDITION(Obj,"bad param"); + auto entry = static_cast(mTable.Search(Obj)); + return entry ? entry->value : nullptr; + } + + inline XPCWrappedNative* Add(XPCWrappedNative* wrapper) + { + NS_PRECONDITION(wrapper,"bad param"); + nsISupports* obj = wrapper->GetIdentityObject(); + MOZ_ASSERT(!Find(obj), "wrapper already in new scope!"); + auto entry = static_cast(mTable.Add(obj, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key) + return entry->value; + entry->key = obj; + entry->value = wrapper; + return wrapper; + } + + inline void Remove(XPCWrappedNative* wrapper) + { + NS_PRECONDITION(wrapper,"bad param"); +#ifdef DEBUG + XPCWrappedNative* wrapperInMap = Find(wrapper->GetIdentityObject()); + MOZ_ASSERT(!wrapperInMap || wrapperInMap == wrapper, + "About to remove a different wrapper with the same " + "nsISupports identity! This will most likely cause serious " + "problems!"); +#endif + mTable.Remove(wrapper->GetIdentityObject()); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + + PLDHashTable::Iterator Iter() { return mTable.Iter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + +private: + Native2WrappedNativeMap(); // no implementation + explicit Native2WrappedNativeMap(int size); + +private: + PLDHashTable mTable; +}; + +/*************************/ + +class IID2WrappedJSClassMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + const nsIID* key; + nsXPCWrappedJSClass* value; + + static const struct PLDHashTableOps sOps; + }; + + static IID2WrappedJSClassMap* newMap(int length); + + inline nsXPCWrappedJSClass* Find(REFNSIID iid) + { + auto entry = static_cast(mTable.Search(&iid)); + return entry ? entry->value : nullptr; + } + + inline nsXPCWrappedJSClass* Add(nsXPCWrappedJSClass* clazz) + { + NS_PRECONDITION(clazz,"bad param"); + const nsIID* iid = &clazz->GetIID(); + auto entry = static_cast(mTable.Add(iid, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key) + return entry->value; + entry->key = iid; + entry->value = clazz; + return clazz; + } + + inline void Remove(nsXPCWrappedJSClass* clazz) + { + NS_PRECONDITION(clazz,"bad param"); + mTable.Remove(&clazz->GetIID()); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + +#ifdef DEBUG + PLDHashTable::Iterator Iter() { return mTable.Iter(); } +#endif + +private: + IID2WrappedJSClassMap(); // no implementation + explicit IID2WrappedJSClassMap(int size); +private: + PLDHashTable mTable; +}; + +/*************************/ + +class IID2NativeInterfaceMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + const nsIID* key; + XPCNativeInterface* value; + + static const struct PLDHashTableOps sOps; + }; + + static IID2NativeInterfaceMap* newMap(int length); + + inline XPCNativeInterface* Find(REFNSIID iid) + { + auto entry = static_cast(mTable.Search(&iid)); + return entry ? entry->value : nullptr; + } + + inline XPCNativeInterface* Add(XPCNativeInterface* iface) + { + NS_PRECONDITION(iface,"bad param"); + const nsIID* iid = iface->GetIID(); + auto entry = static_cast(mTable.Add(iid, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key) + return entry->value; + entry->key = iid; + entry->value = iface; + return iface; + } + + inline void Remove(XPCNativeInterface* iface) + { + NS_PRECONDITION(iface,"bad param"); + mTable.Remove(iface->GetIID()); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + + PLDHashTable::Iterator Iter() { return mTable.Iter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + +private: + IID2NativeInterfaceMap(); // no implementation + explicit IID2NativeInterfaceMap(int size); + +private: + PLDHashTable mTable; +}; + +/*************************/ + +class ClassInfo2NativeSetMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + nsIClassInfo* key; + XPCNativeSet* value; // strong reference + static const PLDHashTableOps sOps; + + private: + static bool Match(const PLDHashEntryHdr* aEntry, const void* aKey); + static void Clear(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + }; + + static ClassInfo2NativeSetMap* newMap(int length); + + inline XPCNativeSet* Find(nsIClassInfo* info) + { + auto entry = static_cast(mTable.Search(info)); + return entry ? entry->value : nullptr; + } + + inline XPCNativeSet* Add(nsIClassInfo* info, XPCNativeSet* set) + { + NS_PRECONDITION(info,"bad param"); + auto entry = static_cast(mTable.Add(info, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key) + return entry->value; + entry->key = info; + NS_ADDREF(entry->value = set); + return set; + } + + inline void Remove(nsIClassInfo* info) + { + NS_PRECONDITION(info,"bad param"); + mTable.Remove(info); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + + // ClassInfo2NativeSetMap holds pointers to *some* XPCNativeSets. + // So we don't want to count those XPCNativeSets, because they are better + // counted elsewhere (i.e. in XPCJSContext::mNativeSetMap, which holds + // pointers to *all* XPCNativeSets). Hence the "Shallow". + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + +private: + ClassInfo2NativeSetMap(); // no implementation + explicit ClassInfo2NativeSetMap(int size); +private: + PLDHashTable mTable; +}; + +/*************************/ + +class ClassInfo2WrappedNativeProtoMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + nsIClassInfo* key; + XPCWrappedNativeProto* value; + }; + + static ClassInfo2WrappedNativeProtoMap* newMap(int length); + + inline XPCWrappedNativeProto* Find(nsIClassInfo* info) + { + auto entry = static_cast(mTable.Search(info)); + return entry ? entry->value : nullptr; + } + + inline XPCWrappedNativeProto* Add(nsIClassInfo* info, XPCWrappedNativeProto* proto) + { + NS_PRECONDITION(info,"bad param"); + auto entry = static_cast(mTable.Add(info, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key) + return entry->value; + entry->key = info; + entry->value = proto; + return proto; + } + + inline void Remove(nsIClassInfo* info) + { + NS_PRECONDITION(info,"bad param"); + mTable.Remove(info); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + + PLDHashTable::Iterator Iter() { return mTable.Iter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + +private: + ClassInfo2WrappedNativeProtoMap(); // no implementation + explicit ClassInfo2WrappedNativeProtoMap(int size); + +private: + PLDHashTable mTable; +}; + +/*************************/ + +class NativeSetMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + XPCNativeSet* key_value; + + static bool + Match(const PLDHashEntryHdr* entry, const void* key); + + static const struct PLDHashTableOps sOps; + }; + + static NativeSetMap* newMap(int length); + + inline XPCNativeSet* Find(XPCNativeSetKey* key) + { + auto entry = static_cast(mTable.Search(key)); + return entry ? entry->key_value : nullptr; + } + + inline XPCNativeSet* Add(const XPCNativeSetKey* key, XPCNativeSet* set) + { + MOZ_ASSERT(key, "bad param"); + MOZ_ASSERT(set, "bad param"); + auto entry = static_cast(mTable.Add(key, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key_value) + return entry->key_value; + entry->key_value = set; + return set; + } + + bool AddNew(const XPCNativeSetKey* key, XPCNativeSet* set) + { + XPCNativeSet* set2 = Add(key, set); + if (!set2) { + return false; + } +#ifdef DEBUG + XPCNativeSetKey key2(set); + MOZ_ASSERT(key->Hash() == key2.Hash()); + MOZ_ASSERT(set2 == set, "Should not have found an existing entry"); +#endif + return true; + } + + inline void Remove(XPCNativeSet* set) + { + MOZ_ASSERT(set, "bad param"); + + XPCNativeSetKey key(set); + mTable.Remove(&key); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + + PLDHashTable::Iterator Iter() { return mTable.Iter(); } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + +private: + NativeSetMap(); // no implementation + explicit NativeSetMap(int size); + +private: + PLDHashTable mTable; +}; + +/***************************************************************************/ + +class IID2ThisTranslatorMap +{ +public: + struct Entry : public PLDHashEntryHdr + { + nsIID key; + nsCOMPtr value; + + static bool + Match(const PLDHashEntryHdr* entry, const void* key); + + static void + Clear(PLDHashTable* table, PLDHashEntryHdr* entry); + + static const struct PLDHashTableOps sOps; + }; + + static IID2ThisTranslatorMap* newMap(int length); + + inline nsIXPCFunctionThisTranslator* Find(REFNSIID iid) + { + auto entry = static_cast(mTable.Search(&iid)); + if (!entry) { + return nullptr; + } + return entry->value; + } + + inline nsIXPCFunctionThisTranslator* Add(REFNSIID iid, + nsIXPCFunctionThisTranslator* obj) + { + auto entry = static_cast(mTable.Add(&iid, mozilla::fallible)); + if (!entry) + return nullptr; + entry->value = obj; + entry->key = iid; + return obj; + } + + inline void Remove(REFNSIID iid) + { + mTable.Remove(&iid); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + +private: + IID2ThisTranslatorMap(); // no implementation + explicit IID2ThisTranslatorMap(int size); +private: + PLDHashTable mTable; +}; + +/***************************************************************************/ + +class XPCWrappedNativeProtoMap +{ +public: + typedef PLDHashEntryStub Entry; + + static XPCWrappedNativeProtoMap* newMap(int length); + + inline XPCWrappedNativeProto* Add(XPCWrappedNativeProto* proto) + { + NS_PRECONDITION(proto,"bad param"); + auto entry = static_cast + (mTable.Add(proto, mozilla::fallible)); + if (!entry) + return nullptr; + if (entry->key) + return (XPCWrappedNativeProto*) entry->key; + entry->key = proto; + return proto; + } + + inline void Remove(XPCWrappedNativeProto* proto) + { + NS_PRECONDITION(proto,"bad param"); + mTable.Remove(proto); + } + + inline uint32_t Count() { return mTable.EntryCount(); } + + PLDHashTable::Iterator Iter() { return mTable.Iter(); } + +private: + XPCWrappedNativeProtoMap(); // no implementation + explicit XPCWrappedNativeProtoMap(int size); +private: + PLDHashTable mTable; +}; + +/***************************************************************************/ + +class JSObject2JSObjectMap +{ + using Map = JS::GCHashMap, + JS::Heap, + js::MovableCellHasher>, + js::SystemAllocPolicy>; + +public: + static JSObject2JSObjectMap* newMap(int length) { + auto* map = new JSObject2JSObjectMap(); + if (!map->mTable.init(length)) { + // This is a decent estimate of the size of the hash table's + // entry storage. The |2| is because on average the capacity is + // twice the requested length. + NS_ABORT_OOM(length * 2 * sizeof(Map::Entry)); + } + return map; + } + + inline JSObject* Find(JSObject* key) { + NS_PRECONDITION(key, "bad param"); + if (Map::Ptr p = mTable.lookup(key)) + return p->value(); + return nullptr; + } + + /* Note: If the entry already exists, return the old value. */ + inline JSObject* Add(JSContext* cx, JSObject* key, JSObject* value) { + NS_PRECONDITION(key,"bad param"); + Map::AddPtr p = mTable.lookupForAdd(key); + if (p) + return p->value(); + if (!mTable.add(p, key, value)) + return nullptr; + MOZ_ASSERT(xpc::CompartmentPrivate::Get(key)->scope->mWaiverWrapperMap == this); + return value; + } + + inline void Remove(JSObject* key) { + NS_PRECONDITION(key,"bad param"); + mTable.remove(key); + } + + inline uint32_t Count() { return mTable.count(); } + + void Sweep() { + mTable.sweep(); + } + +private: + JSObject2JSObjectMap() {} + + Map mTable; +}; + +#endif /* xpcmaps_h___ */ diff --git a/js/xpconnect/src/XPCModule.cpp b/js/xpconnect/src/XPCModule.cpp new file mode 100644 index 000000000..52100b39a --- /dev/null +++ b/js/xpconnect/src/XPCModule.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define XPCONNECT_MODULE +#include "xpcprivate.h" + +nsresult +xpcModuleCtor() +{ + nsXPConnect::InitStatics(); + + return NS_OK; +} + +void +xpcModuleDtor() +{ + // Release our singletons + nsXPConnect::ReleaseXPConnectSingleton(); + xpc_DestroyJSxIDClassObjects(); +} diff --git a/js/xpconnect/src/XPCModule.h b/js/xpconnect/src/XPCModule.h new file mode 100644 index 000000000..d62764625 --- /dev/null +++ b/js/xpconnect/src/XPCModule.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "mozilla/ModuleUtils.h" +#include "mozJSComponentLoader.h" +#include "mozJSSubScriptLoader.h" + +/* Module implementation for the xpconnect library. */ + +#define XPCVARIANT_CONTRACTID "@mozilla.org/xpcvariant;1" + +// {FE4F7592-C1FC-4662-AC83-538841318803} +#define SCRIPTABLE_INTERFACES_CID \ + {0xfe4f7592, 0xc1fc, 0x4662, \ + { 0xac, 0x83, 0x53, 0x88, 0x41, 0x31, 0x88, 0x3 } } + +#define MOZJSSUBSCRIPTLOADER_CONTRACTID "@mozilla.org/moz/jssubscript-loader;1" + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsJSID) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIXPConnect, + nsXPConnect::GetSingleton) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsScriptError) + +NS_GENERIC_FACTORY_CONSTRUCTOR(mozJSComponentLoader) +NS_GENERIC_FACTORY_CONSTRUCTOR(mozJSSubScriptLoader) + +NS_DEFINE_NAMED_CID(NS_JS_ID_CID); +NS_DEFINE_NAMED_CID(NS_XPCONNECT_CID); +NS_DEFINE_NAMED_CID(NS_XPCEXCEPTION_CID); +NS_DEFINE_NAMED_CID(NS_SCRIPTERROR_CID); +NS_DEFINE_NAMED_CID(MOZJSCOMPONENTLOADER_CID); +NS_DEFINE_NAMED_CID(MOZ_JSSUBSCRIPTLOADER_CID); + +#define XPCONNECT_CIDENTRIES \ + { &kNS_JS_ID_CID, false, nullptr, nsJSIDConstructor }, \ + { &kNS_XPCONNECT_CID, false, nullptr, nsIXPConnectConstructor }, \ + { &kNS_SCRIPTERROR_CID, false, nullptr, nsScriptErrorConstructor }, \ + { &kMOZJSCOMPONENTLOADER_CID, false, nullptr, mozJSComponentLoaderConstructor },\ + { &kMOZ_JSSUBSCRIPTLOADER_CID, false, nullptr, mozJSSubScriptLoaderConstructor }, + +#define XPCONNECT_CONTRACTS \ + { XPC_ID_CONTRACTID, &kNS_JS_ID_CID }, \ + { XPC_XPCONNECT_CONTRACTID, &kNS_XPCONNECT_CID }, \ + { XPC_CONTEXT_STACK_CONTRACTID, &kNS_XPCONNECT_CID }, \ + { NS_SCRIPTERROR_CONTRACTID, &kNS_SCRIPTERROR_CID }, \ + { MOZJSCOMPONENTLOADER_CONTRACTID, &kMOZJSCOMPONENTLOADER_CID }, \ + { MOZJSSUBSCRIPTLOADER_CONTRACTID, &kMOZ_JSSUBSCRIPTLOADER_CID }, + +#define XPCONNECT_CATEGORIES \ + { "module-loader", "js", MOZJSCOMPONENTLOADER_CONTRACTID }, + +nsresult xpcModuleCtor(); +void xpcModuleDtor(); diff --git a/js/xpconnect/src/XPCRuntimeService.cpp b/js/xpconnect/src/XPCRuntimeService.cpp new file mode 100644 index 000000000..ce79dcc86 --- /dev/null +++ b/js/xpconnect/src/XPCRuntimeService.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" + +#include "nsContentUtils.h" +#include "BackstagePass.h" +#include "nsDOMClassInfo.h" +#include "nsIPrincipal.h" +#include "mozilla/dom/BindingUtils.h" + +NS_INTERFACE_MAP_BEGIN(BackstagePass) + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable) + NS_INTERFACE_MAP_ENTRY(nsIClassInfo) + NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPCScriptable) +NS_INTERFACE_MAP_END + +NS_IMPL_ADDREF(BackstagePass) +NS_IMPL_RELEASE(BackstagePass) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME BackstagePass +#define XPC_MAP_QUOTED_CLASSNAME "BackstagePass" +#define XPC_MAP_WANT_RESOLVE +#define XPC_MAP_WANT_ENUMERATE +#define XPC_MAP_WANT_FINALIZE +#define XPC_MAP_WANT_PRECREATE + +#define XPC_MAP_FLAGS nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY | \ + nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY | \ + nsIXPCScriptable::DONT_ENUM_QUERY_INTERFACE | \ + nsIXPCScriptable::IS_GLOBAL_OBJECT | \ + nsIXPCScriptable::DONT_REFLECT_INTERFACE_NAMES +#include "xpc_map_end.h" /* This will #undef the above */ + + +JSObject* +BackstagePass::GetGlobalJSObject() +{ + if (mWrapper) + return mWrapper->GetFlatJSObject(); + return nullptr; +} + +void +BackstagePass::SetGlobalObject(JSObject* global) +{ + nsISupports* p = XPCWrappedNative::Get(global); + MOZ_ASSERT(p); + mWrapper = static_cast(p); +} + +NS_IMETHODIMP +BackstagePass::Resolve(nsIXPConnectWrappedNative* wrapper, + JSContext * cx, JSObject * objArg, + jsid idArg, bool* resolvedp, + bool* _retval) +{ + JS::RootedObject obj(cx, objArg); + JS::RootedId id(cx, idArg); + *_retval = mozilla::dom::SystemGlobalResolve(cx, obj, id, resolvedp); + return *_retval ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +BackstagePass::Enumerate(nsIXPConnectWrappedNative* wrapper, JSContext* cx, + JSObject* objArg, bool* _retval) +{ + JS::RootedObject obj(cx, objArg); + *_retval = mozilla::dom::SystemGlobalEnumerate(cx, obj); + return *_retval ? NS_OK : NS_ERROR_FAILURE; +} + +/***************************************************************************/ +NS_IMETHODIMP +BackstagePass::GetInterfaces(uint32_t* aCount, nsIID * **aArray) +{ + const uint32_t count = 2; + *aCount = count; + nsIID** array; + *aArray = array = static_cast(moz_xmalloc(count * sizeof(nsIID*))); + if (!array) + return NS_ERROR_OUT_OF_MEMORY; + + uint32_t index = 0; + nsIID* clone; +#define PUSH_IID(id) \ + clone = static_cast(nsMemory::Clone(&NS_GET_IID( id ), \ + sizeof(nsIID))); \ + if (!clone) \ + goto oom; \ + array[index++] = clone; + + PUSH_IID(nsIXPCScriptable) + PUSH_IID(nsIScriptObjectPrincipal) +#undef PUSH_IID + + return NS_OK; +oom: + while (index) + free(array[--index]); + free(array); + *aArray = nullptr; + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +BackstagePass::GetScriptableHelper(nsIXPCScriptable** retval) +{ + nsCOMPtr scriptable = this; + scriptable.forget(retval); + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetContractID(char * *aContractID) +{ + *aContractID = nullptr; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +BackstagePass::GetClassDescription(char * *aClassDescription) +{ + static const char classDescription[] = "BackstagePass"; + *aClassDescription = (char*)nsMemory::Clone(classDescription, sizeof(classDescription)); + return *aClassDescription ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +BackstagePass::GetClassID(nsCID * *aClassID) +{ + *aClassID = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetFlags(uint32_t* aFlags) +{ + *aFlags = nsIClassInfo::MAIN_THREAD_ONLY; + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +BackstagePass::Finalize(nsIXPConnectWrappedNative* wrapper, JSFreeOp * fop, JSObject * obj) +{ + nsCOMPtr bsp(do_QueryWrappedNative(wrapper)); + MOZ_ASSERT(bsp); + static_cast(bsp.get())->ForgetGlobalObject(); + return NS_OK; +} + +NS_IMETHODIMP +BackstagePass::PreCreate(nsISupports* nativeObj, JSContext* cx, + JSObject* globalObj, JSObject** parentObj) +{ + // We do the same trick here as for WindowSH. Return the js global + // as parent, so XPConenct can find the right scope and the wrapper + // that already exists. + nsCOMPtr global(do_QueryInterface(nativeObj)); + MOZ_ASSERT(global, "nativeObj not a global object!"); + + JSObject* jsglobal = global->GetGlobalJSObject(); + if (jsglobal) + *parentObj = jsglobal; + return NS_OK; +} + +nsresult +NS_NewBackstagePass(BackstagePass** ret) +{ + RefPtr bsp = new BackstagePass( + nsContentUtils::GetSystemPrincipal()); + bsp.forget(ret); + return NS_OK; +} diff --git a/js/xpconnect/src/XPCShellImpl.cpp b/js/xpconnect/src/XPCShellImpl.cpp new file mode 100644 index 000000000..d86b5c5d3 --- /dev/null +++ b/js/xpconnect/src/XPCShellImpl.cpp @@ -0,0 +1,1765 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsXULAppAPI.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "jsprf.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/Preferences.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIXPConnect.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsIDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nscore.h" +#include "nsArrayEnumerator.h" +#include "nsCOMArray.h" +#include "nsDirectoryServiceUtils.h" +#include "nsCOMPtr.h" +#include "nsJSPrincipals.h" +#include "xpcpublic.h" +#include "xpcprivate.h" +#include "BackstagePass.h" +#include "nsIScriptSecurityManager.h" +#include "nsIPrincipal.h" +#include "nsJSUtils.h" +#include "gfxPrefs.h" +#include "nsIXULRuntime.h" + +#include "base/histogram.h" + +#ifdef ANDROID +#include +#endif + +#ifdef XP_WIN +#include "mozilla/widget/AudioSession.h" +#include +#if defined(MOZ_SANDBOX) +#include "SandboxBroker.h" +#endif +#endif + +// all this crap is needed to do the interactive shell stuff +#include +#include +#ifdef HAVE_IO_H +#include /* for isatty() */ +#endif +#ifdef HAVE_UNISTD_H +#include /* for isatty() */ +#endif + +#ifdef MOZ_CRASHREPORTER +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#endif + +using namespace mozilla; +using namespace JS; +using mozilla::dom::AutoJSAPI; +using mozilla::dom::AutoEntryScript; + +class XPCShellDirProvider : public nsIDirectoryServiceProvider2 +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + + XPCShellDirProvider() { } + ~XPCShellDirProvider() { } + + // The platform resource folder + void SetGREDirs(nsIFile* greDir); + void ClearGREDirs() { mGREDir = nullptr; + mGREBinDir = nullptr; } + // The application resource folder + void SetAppDir(nsIFile* appFile); + void ClearAppDir() { mAppDir = nullptr; } + // The app executable + void SetAppFile(nsIFile* appFile); + void ClearAppFile() { mAppFile = nullptr; } + // An additional custom plugin dir if specified + void SetPluginDir(nsIFile* pluginDir); + void ClearPluginDir() { mPluginDir = nullptr; } + +private: + nsCOMPtr mGREDir; + nsCOMPtr mGREBinDir; + nsCOMPtr mAppDir; + nsCOMPtr mPluginDir; + nsCOMPtr mAppFile; +}; + +#ifdef XP_WIN +class MOZ_STACK_CLASS AutoAudioSession +{ +public: + AutoAudioSession() { + widget::StartAudioSession(); + } + + ~AutoAudioSession() { + widget::StopAudioSession(); + } +}; +#endif + +static const char kXPConnectServiceContractID[] = "@mozilla.org/js/xpc/XPConnect;1"; + +#define EXITCODE_RUNTIME_ERROR 3 +#define EXITCODE_FILE_NOT_FOUND 4 + +static FILE* gOutFile = nullptr; +static FILE* gErrFile = nullptr; +static FILE* gInFile = nullptr; + +static int gExitCode = 0; +static bool gQuitting = false; +static bool reportWarnings = true; +static bool compileOnly = false; + +static JSPrincipals* gJSPrincipals = nullptr; +static nsAutoString* gWorkingDirectory = nullptr; + +static bool +GetLocationProperty(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.thisv().isObject()) { + JS_ReportErrorASCII(cx, "Unexpected this value for GetLocationProperty"); + return false; + } +#if !defined(XP_WIN) && !defined(XP_UNIX) + //XXX: your platform should really implement this + return false; +#else + JS::AutoFilename filename; + if (JS::DescribeScriptedCaller(cx, &filename) && filename.get()) { + nsresult rv; + nsCOMPtr xpc = + do_GetService(kXPConnectServiceContractID, &rv); + +#if defined(XP_WIN) + // convert from the system codepage to UTF-16 + int bufferSize = MultiByteToWideChar(CP_ACP, 0, filename.get(), + -1, nullptr, 0); + nsAutoString filenameString; + filenameString.SetLength(bufferSize); + MultiByteToWideChar(CP_ACP, 0, filename.get(), + -1, (LPWSTR)filenameString.BeginWriting(), + filenameString.Length()); + // remove the null terminator + filenameString.SetLength(bufferSize - 1); + + // replace forward slashes with backslashes, + // since nsLocalFileWin chokes on them + char16_t* start = filenameString.BeginWriting(); + char16_t* end = filenameString.EndWriting(); + + while (start != end) { + if (*start == L'/') + *start = L'\\'; + start++; + } +#elif defined(XP_UNIX) + NS_ConvertUTF8toUTF16 filenameString(filename.get()); +#endif + + nsCOMPtr location; + if (NS_SUCCEEDED(rv)) { + rv = NS_NewLocalFile(filenameString, + false, getter_AddRefs(location)); + } + + if (!location && gWorkingDirectory) { + // could be a relative path, try appending it to the cwd + // and then normalize + nsAutoString absolutePath(*gWorkingDirectory); + absolutePath.Append(filenameString); + + rv = NS_NewLocalFile(absolutePath, + false, getter_AddRefs(location)); + } + + if (location) { + bool symlink; + // don't normalize symlinks, because that's kind of confusing + if (NS_SUCCEEDED(location->IsSymlink(&symlink)) && + !symlink) + location->Normalize(); + RootedObject locationObj(cx); + rv = xpc->WrapNative(cx, &args.thisv().toObject(), location, + NS_GET_IID(nsIFile), locationObj.address()); + if (NS_SUCCEEDED(rv) && locationObj) { + args.rval().setObject(*locationObj); + } + } + } + + return true; +#endif +} + +static bool +GetLine(JSContext* cx, char* bufp, FILE* file, const char* prompt) +{ + fputs(prompt, gOutFile); + fflush(gOutFile); + + char line[4096] = { '\0' }; + while (true) { + if (fgets(line, sizeof line, file)) { + strcpy(bufp, line); + return true; + } + if (errno != EINTR) { + return false; + } + } +} + +static bool +ReadLine(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // While 4096 might be quite arbitrary, this is something to be fixed in + // bug 105707. It is also the same limit as in ProcessFile. + char buf[4096]; + RootedString str(cx); + + /* If a prompt was specified, construct the string */ + if (args.length() > 0) { + str = JS::ToString(cx, args[0]); + if (!str) + return false; + } else { + str = JS_GetEmptyString(cx); + } + + /* Get a line from the infile */ + JSAutoByteString strBytes(cx, str); + if (!strBytes || !GetLine(cx, buf, gInFile, strBytes.ptr())) + return false; + + /* Strip newline character added by GetLine() */ + unsigned int buflen = strlen(buf); + if (buflen == 0) { + if (feof(gInFile)) { + args.rval().setNull(); + return true; + } + } else if (buf[buflen - 1] == '\n') { + --buflen; + } + + /* Turn buf into a JSString */ + str = JS_NewStringCopyN(cx, buf, buflen); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +static bool +Print(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + + RootedString str(cx); + nsAutoCString utf8output; + + for (unsigned i = 0; i < args.length(); i++) { + str = ToString(cx, args[i]); + if (!str) + return false; + + JSAutoByteString utf8str; + if (!utf8str.encodeUtf8(cx, str)) + return false; + + if (i) + utf8output.Append(' '); + utf8output.Append(utf8str.ptr(), utf8str.length()); + } + utf8output.Append('\n'); + fputs(utf8output.get(), gOutFile); + fflush(gOutFile); + return true; +} + +static bool +Dump(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setUndefined(); + + if (!args.length()) + return true; + + RootedString str(cx, ToString(cx, args[0])); + if (!str) + return false; + + JSAutoByteString utf8str; + if (!utf8str.encodeUtf8(cx, str)) + return false; + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", utf8str.ptr()); +#endif +#ifdef XP_WIN + if (IsDebuggerPresent()) { + nsAutoJSString wstr; + if (!wstr.init(cx, str)) + return false; + OutputDebugStringW(wstr.get()); + } +#endif + fputs(utf8str.ptr(), gOutFile); + fflush(gOutFile); + return true; +} + +static bool +Load(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + JS::Rooted obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + if (!JS_IsGlobalObject(obj)) { + JS_ReportErrorASCII(cx, "Trying to load() into a non-global object"); + return false; + } + + RootedString str(cx); + for (unsigned i = 0; i < args.length(); i++) { + str = ToString(cx, args[i]); + if (!str) + return false; + JSAutoByteString filename(cx, str); + if (!filename) + return false; + FILE* file = fopen(filename.ptr(), "r"); + if (!file) { + filename.clear(); + if (!filename.encodeUtf8(cx, str)) + return false; + JS_ReportErrorUTF8(cx, "cannot open file '%s' for reading", + filename.ptr()); + return false; + } + JS::CompileOptions options(cx); + options.setUTF8(true) + .setFileAndLine(filename.ptr(), 1) + .setIsRunOnce(true); + JS::Rooted script(cx); + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + JS::Compile(cx, options, file, &script); + fclose(file); + if (!script) + return false; + + if (!compileOnly) { + if (!JS_ExecuteScript(cx, script)) { + return false; + } + } + } + args.rval().setUndefined(); + return true; +} + +static bool +Version(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + args.rval().setInt32(JS_GetVersion(cx)); + if (args.get(0).isInt32()) + JS_SetVersionForCompartment(js::GetContextCompartment(cx), + JSVersion(args[0].toInt32())); + return true; +} + +static bool +Quit(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + gExitCode = 0; + if (!ToInt32(cx, args.get(0), &gExitCode)) + return false; + + gQuitting = true; +// exit(0); + return false; +} + +static bool +DumpXPC(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + + uint16_t depth = 2; + if (args.length() > 0) { + if (!JS::ToUint16(cx, args[0], &depth)) + return false; + } + + nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); + if (xpc) + xpc->DebugDump(int16_t(depth)); + args.rval().setUndefined(); + return true; +} + +static bool +GC(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + JS_GC(cx); + + args.rval().setUndefined(); + return true; +} + +#ifdef JS_GC_ZEAL +static bool +GCZeal(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + uint32_t zeal; + if (!ToUint32(cx, args.get(0), &zeal)) + return false; + + JS_SetGCZeal(cx, uint8_t(zeal), JS_DEFAULT_ZEAL_FREQ); + args.rval().setUndefined(); + return true; +} +#endif + +static bool +SendCommand(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (args.length() == 0) { + JS_ReportErrorASCII(cx, "Function takes at least one argument!"); + return false; + } + + RootedString str(cx, ToString(cx, args[0])); + if (!str) { + JS_ReportErrorASCII(cx, "Could not convert argument 1 to string!"); + return false; + } + + if (args.length() > 1 && JS_TypeOfValue(cx, args[1]) != JSTYPE_FUNCTION) { + JS_ReportErrorASCII(cx, "Could not convert argument 2 to function!"); + return false; + } + + if (!XRE_SendTestShellCommand(cx, str, args.length() > 1 ? args[1].address() : nullptr)) { + JS_ReportErrorASCII(cx, "Couldn't send command!"); + return false; + } + + args.rval().setUndefined(); + return true; +} + +static bool +Options(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + ContextOptions oldContextOptions = ContextOptionsRef(cx); + + RootedString str(cx); + JSAutoByteString opt; + for (unsigned i = 0; i < args.length(); ++i) { + str = ToString(cx, args[i]); + if (!str) + return false; + + opt.clear(); + if (!opt.encodeUtf8(cx, str)) + return false; + + if (strcmp(opt.ptr(), "strict") == 0) + ContextOptionsRef(cx).toggleExtraWarnings(); + else if (strcmp(opt.ptr(), "werror") == 0) + ContextOptionsRef(cx).toggleWerror(); + else if (strcmp(opt.ptr(), "strict_mode") == 0) + ContextOptionsRef(cx).toggleStrictMode(); + else { + JS_ReportErrorUTF8(cx, "unknown option name '%s'. The valid names are " + "strict, werror, and strict_mode.", opt.ptr()); + return false; + } + } + + char* names = nullptr; + if (oldContextOptions.extraWarnings()) { + names = JS_sprintf_append(names, "%s", "strict"); + if (!names) { + JS_ReportOutOfMemory(cx); + return false; + } + } + if (oldContextOptions.werror()) { + names = JS_sprintf_append(names, "%s%s", names ? "," : "", "werror"); + if (!names) { + JS_ReportOutOfMemory(cx); + return false; + } + } + if (names && oldContextOptions.strictMode()) { + names = JS_sprintf_append(names, "%s%s", names ? "," : "", "strict_mode"); + if (!names) { + JS_ReportOutOfMemory(cx); + return false; + } + } + + str = JS_NewStringCopyZ(cx, names); + free(names); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +static PersistentRootedValue *sScriptedInterruptCallback = nullptr; + +static bool +XPCShellInterruptCallback(JSContext* cx) +{ + MOZ_ASSERT(sScriptedInterruptCallback->initialized()); + RootedValue callback(cx, *sScriptedInterruptCallback); + + // If no interrupt callback was set by script, no-op. + if (callback.isUndefined()) + return true; + + JSAutoCompartment ac(cx, &callback.toObject()); + RootedValue rv(cx); + if (!JS_CallFunctionValue(cx, nullptr, callback, JS::HandleValueArray::empty(), &rv) || + !rv.isBoolean()) + { + NS_WARNING("Scripted interrupt callback failed! Terminating script."); + JS_ClearPendingException(cx); + return false; + } + + return rv.toBoolean(); +} + +static bool +SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp) +{ + MOZ_ASSERT(sScriptedInterruptCallback->initialized()); + + // Sanity-check args. + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorASCII(cx, "Wrong number of arguments"); + return false; + } + + // Allow callers to remove the interrupt callback by passing undefined. + if (args[0].isUndefined()) { + *sScriptedInterruptCallback = UndefinedValue(); + return true; + } + + // Otherwise, we should have a callable object. + if (!args[0].isObject() || !JS::IsCallable(&args[0].toObject())) { + JS_ReportErrorASCII(cx, "Argument must be callable"); + return false; + } + + *sScriptedInterruptCallback = args[0]; + + return true; +} + +static bool +SimulateActivityCallback(JSContext* cx, unsigned argc, Value* vp) +{ + // Sanity-check args. + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1 || !args[0].isBoolean()) { + JS_ReportErrorASCII(cx, "Wrong number of arguments"); + return false; + } + xpc::SimulateActivityCallback(args[0].toBoolean()); + return true; +} + +static bool +RegisterAppManifest(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1) { + JS_ReportErrorASCII(cx, "Wrong number of arguments"); + return false; + } + if (!args[0].isObject()) { + JS_ReportErrorASCII(cx, "Expected object as argument 1 to registerAppManifest"); + return false; + } + + Rooted arg1(cx, &args[0].toObject()); + nsCOMPtr file; + nsresult rv = nsXPConnect::XPConnect()-> + WrapJS(cx, arg1, NS_GET_IID(nsIFile), getter_AddRefs(file)); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + rv = XRE_AddManifestLocation(NS_APP_LOCATION, file); + if (NS_FAILED(rv)) { + XPCThrower::Throw(rv, cx); + return false; + } + return true; +} + +static const JSFunctionSpec glob_functions[] = { + JS_FS("print", Print, 0,0), + JS_FS("readline", ReadLine, 1,0), + JS_FS("load", Load, 1,0), + JS_FS("quit", Quit, 0,0), + JS_FS("version", Version, 1,0), + JS_FS("dumpXPC", DumpXPC, 1,0), + JS_FS("dump", Dump, 1,0), + JS_FS("gc", GC, 0,0), +#ifdef JS_GC_ZEAL + JS_FS("gczeal", GCZeal, 1,0), +#endif + JS_FS("options", Options, 0,0), + JS_FS("sendCommand", SendCommand, 1,0), + JS_FS("atob", xpc::Atob, 1,0), + JS_FS("btoa", xpc::Btoa, 1,0), + JS_FS("setInterruptCallback", SetInterruptCallback, 1,0), + JS_FS("simulateActivityCallback", SimulateActivityCallback, 1,0), + JS_FS("registerAppManifest", RegisterAppManifest, 1, 0), + JS_FS_END +}; + +static bool +env_setProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp, + ObjectOpResult& result) +{ +/* XXX porting may be easy, but these don't seem to supply setenv by default */ +#if !defined SOLARIS + RootedString valstr(cx); + RootedString idstr(cx); + int rv; + + RootedValue idval(cx); + if (!JS_IdToValue(cx, id, &idval)) + return false; + + idstr = ToString(cx, idval); + valstr = ToString(cx, vp); + if (!idstr || !valstr) + return false; + JSAutoByteString name(cx, idstr); + if (!name) + return false; + JSAutoByteString value(cx, valstr); + if (!value) + return false; +#if defined XP_WIN || defined HPUX || defined OSF1 || defined SCO + { + char* waste = JS_smprintf("%s=%s", name.ptr(), value.ptr()); + if (!waste) { + JS_ReportOutOfMemory(cx); + return false; + } + rv = putenv(waste); +#ifdef XP_WIN + /* + * HPUX9 at least still has the bad old non-copying putenv. + * + * Per mail from , OSF1 also has a putenv + * that will crash if you pass it an auto char array (so it must place + * its argument directly in the char* environ[] array). + */ + free(waste); +#endif + } +#else + rv = setenv(name.ptr(), value.ptr(), 1); +#endif + if (rv < 0) { + name.clear(); + value.clear(); + if (!name.encodeUtf8(cx, idstr)) + return false; + if (!value.encodeUtf8(cx, valstr)) + return false; + JS_ReportErrorUTF8(cx, "can't set envariable %s to %s", name.ptr(), value.ptr()); + return false; + } + vp.setString(valstr); +#endif /* !defined SOLARIS */ + return result.succeed(); +} + +static bool +env_enumerate(JSContext* cx, HandleObject obj) +{ + static bool reflected; + char** evp; + char* name; + char* value; + RootedString valstr(cx); + bool ok; + + if (reflected) + return true; + + for (evp = (char**)JS_GetPrivate(obj); (name = *evp) != nullptr; evp++) { + value = strchr(name, '='); + if (!value) + continue; + *value++ = '\0'; + valstr = JS_NewStringCopyZ(cx, value); + ok = valstr ? JS_DefineProperty(cx, obj, name, valstr, JSPROP_ENUMERATE) : false; + value[-1] = '='; + if (!ok) + return false; + } + + reflected = true; + return true; +} + +static bool +env_resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +{ + JSString* idstr; + + RootedValue idval(cx); + if (!JS_IdToValue(cx, id, &idval)) + return false; + + idstr = ToString(cx, idval); + if (!idstr) + return false; + JSAutoByteString name(cx, idstr); + if (!name) + return false; + const char* value = getenv(name.ptr()); + if (value) { + RootedString valstr(cx, JS_NewStringCopyZ(cx, value)); + if (!valstr) + return false; + if (!JS_DefinePropertyById(cx, obj, id, valstr, JSPROP_ENUMERATE)) { + return false; + } + *resolvedp = true; + } + return true; +} + +static const JSClassOps env_classOps = { + nullptr, nullptr, nullptr, env_setProperty, + env_enumerate, env_resolve +}; + +static const JSClass env_class = { + "environment", JSCLASS_HAS_PRIVATE, + &env_classOps +}; + +/***************************************************************************/ + +typedef enum JSShellErrNum { +#define MSG_DEF(name, number, count, exception, format) \ + name = number, +#include "jsshell.msg" +#undef MSG_DEF + JSShellErr_Limit +} JSShellErrNum; + +static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = { +#define MSG_DEF(name, number, count, exception, format) \ + { #name, format, count } , +#include "jsshell.msg" +#undef MSG_DEF +}; + +static const JSErrorFormatString* +my_GetErrorMessage(void* userRef, const unsigned errorNumber) +{ + if (errorNumber == 0 || errorNumber >= JSShellErr_Limit) + return nullptr; + + return &jsShell_ErrorFormatString[errorNumber]; +} + +static bool +ProcessLine(AutoJSAPI& jsapi, const char* buffer, int startline) +{ + JSContext* cx = jsapi.cx(); + JS::RootedScript script(cx); + JS::RootedValue result(cx); + JS::CompileOptions options(cx); + options.setFileAndLine("typein", startline) + .setIsRunOnce(true); + if (!JS_CompileScript(cx, buffer, strlen(buffer), options, &script)) + return false; + if (compileOnly) + return true; + if (!JS_ExecuteScript(cx, script, &result)) + return false; + + if (result.isUndefined()) + return true; + RootedString str(cx); + if (!(str = ToString(cx, result))) + return false; + JSAutoByteString bytes; + if (!bytes.encodeLatin1(cx, str)) + return false; + + fprintf(gOutFile, "%s\n", bytes.ptr()); + return true; +} + +static bool +ProcessFile(AutoJSAPI& jsapi, const char* filename, FILE* file, bool forceTTY) +{ + JSContext* cx = jsapi.cx(); + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + MOZ_ASSERT(global); + + if (forceTTY) { + file = stdin; + } else if (!isatty(fileno(file))) { + /* + * It's not interactive - just execute it. + * + * Support the UNIX #! shell hack; gobble the first line if it starts + * with '#'. TODO - this isn't quite compatible with sharp variables, + * as a legal js program (using sharp variables) might start with '#'. + * But that would require multi-character lookahead. + */ + int ch = fgetc(file); + if (ch == '#') { + while ((ch = fgetc(file)) != EOF) { + if (ch == '\n' || ch == '\r') + break; + } + } + ungetc(ch, file); + + JS::RootedScript script(cx); + JS::RootedValue unused(cx); + JS::CompileOptions options(cx); + options.setUTF8(true) + .setFileAndLine(filename, 1) + .setIsRunOnce(true) + .setNoScriptRval(true); + if (!JS::Compile(cx, options, file, &script)) + return false; + return compileOnly || JS_ExecuteScript(cx, script, &unused); + } + + /* It's an interactive filehandle; drop into read-eval-print loop. */ + int lineno = 1; + bool hitEOF = false; + do { + char buffer[4096]; + char* bufp = buffer; + *bufp = '\0'; + + /* + * Accumulate lines until we get a 'compilable unit' - one that either + * generates an error (before running out of source) or that compiles + * cleanly. This should be whenever we get a complete statement that + * coincides with the end of a line. + */ + int startline = lineno; + do { + if (!GetLine(cx, bufp, file, startline == lineno ? "js> " : "")) { + hitEOF = true; + break; + } + bufp += strlen(bufp); + lineno++; + } while (!JS_BufferIsCompilableUnit(cx, global, buffer, strlen(buffer))); + + if (!ProcessLine(jsapi, buffer, startline)) + jsapi.ReportException(); + } while (!hitEOF && !gQuitting); + + fprintf(gOutFile, "\n"); + return true; +} + +static bool +Process(AutoJSAPI& jsapi, const char* filename, bool forceTTY) +{ + FILE* file; + + if (forceTTY || !filename || strcmp(filename, "-") == 0) { + file = stdin; + } else { + file = fopen(filename, "r"); + if (!file) { + /* + * Use Latin1 variant here because the encoding of the return value + * of strerror function can be non-UTF-8. + */ + JS_ReportErrorNumberLatin1(jsapi.cx(), my_GetErrorMessage, nullptr, + JSSMSG_CANT_OPEN, + filename, strerror(errno)); + gExitCode = EXITCODE_FILE_NOT_FOUND; + return false; + } + } + + bool ok = ProcessFile(jsapi, filename, file, forceTTY); + if (file != stdin) + fclose(file); + return ok; +} + +static int +usage() +{ + fprintf(gErrFile, "%s\n", JS_GetImplementationVersion()); + fprintf(gErrFile, "usage: xpcshell [-g gredir] [-a appdir] [-r manifest]... [-WwxiCSsmIp] [-v version] [-f scriptfile] [-e script] [scriptfile] [scriptarg...]\n"); + return 2; +} + +static bool +printUsageAndSetExitCode() +{ + gExitCode = usage(); + return false; +} + +static void +ProcessArgsForCompartment(JSContext* cx, char** argv, int argc) +{ + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') + break; + + switch (argv[i][1]) { + case 'v': + case 'f': + case 'e': + if (++i == argc) + return; + break; + case 'S': + ContextOptionsRef(cx).toggleWerror(); + MOZ_FALLTHROUGH; // because -S implies -s + case 's': + ContextOptionsRef(cx).toggleExtraWarnings(); + break; + case 'I': + ContextOptionsRef(cx).toggleIon() + .toggleAsmJS() + .toggleWasm(); + break; + } + } +} + +static bool +ProcessArgs(AutoJSAPI& jsapi, char** argv, int argc, XPCShellDirProvider* aDirProvider) +{ + JSContext* cx = jsapi.cx(); + const char rcfilename[] = "xpcshell.js"; + FILE* rcfile; + int rootPosition; + JS::Rooted argsObj(cx); + char* filename = nullptr; + bool isInteractive = true; + bool forceTTY = false; + + rcfile = fopen(rcfilename, "r"); + if (rcfile) { + printf("[loading '%s'...]\n", rcfilename); + bool ok = ProcessFile(jsapi, rcfilename, rcfile, false); + fclose(rcfile); + if (!ok) { + return false; + } + } + + JS::Rooted global(cx, JS::CurrentGlobalOrNull(cx)); + + /* + * Scan past all optional arguments so we can create the arguments object + * before processing any -f options, which must interleave properly with + * -v and -w options. This requires two passes, and without getopt, we'll + * have to keep the option logic here and in the second for loop in sync. + * First of all, find out the first argument position which will be passed + * as a script file to be executed. + */ + for (rootPosition = 0; rootPosition < argc; rootPosition++) { + if (argv[rootPosition][0] != '-' || argv[rootPosition][1] == '\0') { + ++rootPosition; + break; + } + + bool isPairedFlag = + argv[rootPosition][0] != '\0' && + (argv[rootPosition][1] == 'v' || + argv[rootPosition][1] == 'f' || + argv[rootPosition][1] == 'e'); + if (isPairedFlag && rootPosition < argc - 1) { + ++rootPosition; // Skip over the 'foo' portion of |-v foo|, |-f foo|, or |-e foo|. + } + } + + /* + * Create arguments early and define it to root it, so it's safe from any + * GC calls nested below, and so it is available to -f arguments. + */ + argsObj = JS_NewArrayObject(cx, 0); + if (!argsObj) + return 1; + if (!JS_DefineProperty(cx, global, "arguments", argsObj, 0)) + return 1; + + for (int j = 0, length = argc - rootPosition; j < length; j++) { + RootedString str(cx, JS_NewStringCopyZ(cx, argv[rootPosition++])); + if (!str || + !JS_DefineElement(cx, argsObj, j, str, JSPROP_ENUMERATE)) { + return 1; + } + } + + for (int i = 0; i < argc; i++) { + if (argv[i][0] != '-' || argv[i][1] == '\0') { + filename = argv[i++]; + isInteractive = false; + break; + } + switch (argv[i][1]) { + case 'v': + if (++i == argc) { + return printUsageAndSetExitCode(); + } + JS_SetVersionForCompartment(js::GetContextCompartment(cx), + JSVersion(atoi(argv[i]))); + break; + case 'W': + reportWarnings = false; + break; + case 'w': + reportWarnings = true; + break; + case 'x': + break; + case 'd': + /* This used to try to turn on the debugger. */ + break; + case 'm': + break; + case 'f': + if (++i == argc) { + return printUsageAndSetExitCode(); + } + if (!Process(jsapi, argv[i], false)) + return false; + /* + * XXX: js -f foo.js should interpret foo.js and then + * drop into interactive mode, but that breaks test + * harness. Just execute foo.js for now. + */ + isInteractive = false; + break; + case 'i': + isInteractive = forceTTY = true; + break; + case 'e': + { + RootedValue rval(cx); + + if (++i == argc) { + return printUsageAndSetExitCode(); + } + + JS::CompileOptions opts(cx); + opts.setFileAndLine("-e", 1); + JS::Evaluate(cx, opts, argv[i], strlen(argv[i]), &rval); + + isInteractive = false; + break; + } + case 'C': + compileOnly = true; + isInteractive = false; + break; + case 'S': + case 's': + case 'I': + // These options are processed in ProcessArgsForCompartment. + break; + case 'p': + { + // plugins path + char* pluginPath = argv[++i]; + nsCOMPtr pluginsDir; + if (NS_FAILED(XRE_GetFileFromPath(pluginPath, getter_AddRefs(pluginsDir)))) { + fprintf(gErrFile, "Couldn't use given plugins dir.\n"); + return printUsageAndSetExitCode(); + } + aDirProvider->SetPluginDir(pluginsDir); + break; + } + default: + return printUsageAndSetExitCode(); + } + } + + if (filename || isInteractive) + return Process(jsapi, filename, forceTTY); + return true; +} + +/***************************************************************************/ + +// #define TEST_InitClassesWithNewWrappedGlobal + +#ifdef TEST_InitClassesWithNewWrappedGlobal +// XXX hacky test code... +#include "xpctest.h" + +class TestGlobal : public nsIXPCTestNoisy, public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCTESTNOISY + NS_DECL_NSIXPCSCRIPTABLE + + TestGlobal(){} +}; + +NS_IMPL_ISUPPORTS(TestGlobal, nsIXPCTestNoisy, nsIXPCScriptable) + +// The nsIXPCScriptable map declaration that will generate stubs for us... +#define XPC_MAP_CLASSNAME TestGlobal +#define XPC_MAP_QUOTED_CLASSNAME "TestGlobal" +#define XPC_MAP_FLAGS nsIXPCScriptable::USE_JSSTUB_FOR_ADDPROPERTY |\ + nsIXPCScriptable::USE_JSSTUB_FOR_DELPROPERTY |\ + nsIXPCScriptable::USE_JSSTUB_FOR_SETPROPERTY +#include "xpc_map_end.h" /* This will #undef the above */ + +NS_IMETHODIMP TestGlobal::Squawk() {return NS_OK;} + +#endif + +// uncomment to install the test 'this' translator +// #define TEST_TranslateThis + +#ifdef TEST_TranslateThis + +#include "xpctest.h" + +class nsXPCFunctionThisTranslator : public nsIXPCFunctionThisTranslator +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCFUNCTIONTHISTRANSLATOR + + nsXPCFunctionThisTranslator(); + virtual ~nsXPCFunctionThisTranslator(); + /* additional members */ +}; + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsXPCFunctionThisTranslator, nsIXPCFunctionThisTranslator) + +nsXPCFunctionThisTranslator::nsXPCFunctionThisTranslator() +{ +} + +nsXPCFunctionThisTranslator::~nsXPCFunctionThisTranslator() +{ +} + +NS_IMETHODIMP +nsXPCFunctionThisTranslator::TranslateThis(nsISupports* aInitialThis, + nsISupports** _retval) +{ + nsCOMPtr temp = aInitialThis; + temp.forget(_retval); + return NS_OK; +} + +#endif + +static bool +GetCurrentWorkingDirectory(nsAString& workingDirectory) +{ +#if !defined(XP_WIN) && !defined(XP_UNIX) + //XXX: your platform should really implement this + return false; +#elif XP_WIN + DWORD requiredLength = GetCurrentDirectoryW(0, nullptr); + workingDirectory.SetLength(requiredLength); + GetCurrentDirectoryW(workingDirectory.Length(), + (LPWSTR)workingDirectory.BeginWriting()); + // we got a trailing null there + workingDirectory.SetLength(requiredLength); + workingDirectory.Replace(workingDirectory.Length() - 1, 1, L'\\'); +#elif defined(XP_UNIX) + nsAutoCString cwd; + // 1024 is just a guess at a sane starting value + size_t bufsize = 1024; + char* result = nullptr; + while (result == nullptr) { + cwd.SetLength(bufsize); + result = getcwd(cwd.BeginWriting(), cwd.Length()); + if (!result) { + if (errno != ERANGE) + return false; + // need to make the buffer bigger + bufsize *= 2; + } + } + // size back down to the actual string length + cwd.SetLength(strlen(result) + 1); + cwd.Replace(cwd.Length() - 1, 1, '/'); + workingDirectory = NS_ConvertUTF8toUTF16(cwd); +#endif + return true; +} + +static JSSecurityCallbacks shellSecurityCallbacks; + +int +XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData) +{ + MOZ_ASSERT(aShellData); + + JSContext* cx; + int result = 0; + nsresult rv; + + gErrFile = stderr; + gOutFile = stdout; + gInFile = stdin; + + NS_LogInit(); + + mozilla::LogModule::Init(); + + // A initializer to initialize histogram collection + // used by telemetry. + UniquePtr telStats = + MakeUnique(); + + if (PR_GetEnv("MOZ_CHAOSMODE")) { + ChaosFeature feature = ChaosFeature::Any; + long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16); + if (featureInt) { + // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature. + feature = static_cast(featureInt); + } + ChaosMode::SetChaosFeature(feature); + } + + if (ChaosMode::isActive(ChaosFeature::Any)) { + printf_stderr("*** You are running in chaos test mode. See ChaosMode.h. ***\n"); + } + + nsCOMPtr appFile; + rv = XRE_GetBinaryPath(argv[0], getter_AddRefs(appFile)); + if (NS_FAILED(rv)) { + printf("Couldn't find application file.\n"); + return 1; + } + nsCOMPtr appDir; + rv = appFile->GetParent(getter_AddRefs(appDir)); + if (NS_FAILED(rv)) { + printf("Couldn't get application directory.\n"); + return 1; + } + + XPCShellDirProvider dirprovider; + + dirprovider.SetAppFile(appFile); + + nsCOMPtr greDir; + if (argc > 1 && !strcmp(argv[1], "-g")) { + if (argc < 3) + return usage(); + + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + printf("Couldn't use given GRE dir.\n"); + return 1; + } + + dirprovider.SetGREDirs(greDir); + + argc -= 2; + argv += 2; + } else { +#ifdef XP_MACOSX + // On OSX, the GreD needs to point to Contents/Resources in the .app + // bundle. Libraries will be loaded at a relative path to GreD, i.e. + // ../MacOS. + nsCOMPtr tmpDir; + XRE_GetFileFromPath(argv[0], getter_AddRefs(greDir)); + greDir->GetParent(getter_AddRefs(tmpDir)); + tmpDir->Clone(getter_AddRefs(greDir)); + tmpDir->SetNativeLeafName(NS_LITERAL_CSTRING("Resources")); + bool dirExists = false; + tmpDir->Exists(&dirExists); + if (dirExists) { + greDir = tmpDir.forget(); + } + dirprovider.SetGREDirs(greDir); +#else + nsAutoString workingDir; + if (!GetCurrentWorkingDirectory(workingDir)) { + printf("GetCurrentWorkingDirectory failed.\n"); + return 1; + } + rv = NS_NewLocalFile(workingDir, true, getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + printf("NS_NewLocalFile failed.\n"); + return 1; + } +#endif + } + + if (argc > 1 && !strcmp(argv[1], "-a")) { + if (argc < 3) + return usage(); + + nsCOMPtr dir; + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(dir)); + if (NS_SUCCEEDED(rv)) { + appDir = do_QueryInterface(dir, &rv); + dirprovider.SetAppDir(appDir); + } + if (NS_FAILED(rv)) { + printf("Couldn't use given appdir.\n"); + return 1; + } + argc -= 2; + argv += 2; + } + + while (argc > 1 && !strcmp(argv[1], "-r")) { + if (argc < 3) + return usage(); + + nsCOMPtr lf; + rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf)); + if (NS_FAILED(rv)) { + printf("Couldn't get manifest file.\n"); + return 1; + } + XRE_AddManifestLocation(NS_APP_LOCATION, lf); + + argc -= 2; + argv += 2; + } + +#ifdef MOZ_CRASHREPORTER + const char* val = getenv("MOZ_CRASHREPORTER"); + if (val && *val) { + rv = CrashReporter::SetExceptionHandler(greDir, true); + if (NS_FAILED(rv)) { + printf("CrashReporter::SetExceptionHandler failed!\n"); + return 1; + } + MOZ_ASSERT(CrashReporter::GetEnabled()); + } +#endif + + { + if (argc > 1 && !strcmp(argv[1], "--greomni")) { + nsCOMPtr greOmni; + nsCOMPtr appOmni; + XRE_GetFileFromPath(argv[2], getter_AddRefs(greOmni)); + if (argc > 3 && !strcmp(argv[3], "--appomni")) { + XRE_GetFileFromPath(argv[4], getter_AddRefs(appOmni)); + argc-=2; + argv+=2; + } else { + appOmni = greOmni; + } + + XRE_InitOmnijar(greOmni, appOmni); + argc-=2; + argv+=2; + } + + nsCOMPtr servMan; + rv = NS_InitXPCOM2(getter_AddRefs(servMan), appDir, &dirprovider); + if (NS_FAILED(rv)) { + printf("NS_InitXPCOM2 failed!\n"); + return 1; + } + + // xpc::ErrorReport::LogToConsoleWithStack needs this to print errors + // to stderr. + Preferences::SetBool("browser.dom.window.dump.enabled", true); + + AutoJSAPI jsapi; + jsapi.Init(); + cx = jsapi.cx(); + + // Override the default XPConnect interrupt callback. We could store the + // old one and restore it before shutting down, but there's not really a + // reason to bother. + sScriptedInterruptCallback = new PersistentRootedValue; + sScriptedInterruptCallback->init(cx, UndefinedValue()); + + JS_AddInterruptCallback(cx, XPCShellInterruptCallback); + + argc--; + argv++; + ProcessArgsForCompartment(cx, argv, argc); + + nsCOMPtr xpc = do_GetService(nsIXPConnect::GetCID()); + if (!xpc) { + printf("failed to get nsXPConnect service!\n"); + return 1; + } + + nsCOMPtr systemprincipal; + // Fetch the system principal and store it away in a global, to use for + // script compilation in Load() and ProcessFile() (including interactive + // eval loop) + { + + nsCOMPtr securityManager = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && securityManager) { + rv = securityManager->GetSystemPrincipal(getter_AddRefs(systemprincipal)); + if (NS_FAILED(rv)) { + fprintf(gErrFile, "+++ Failed to obtain SystemPrincipal from ScriptSecurityManager service.\n"); + } else { + // fetch the JS principals and stick in a global + gJSPrincipals = nsJSPrincipals::get(systemprincipal); + JS_HoldPrincipals(gJSPrincipals); + } + } else { + fprintf(gErrFile, "+++ Failed to get ScriptSecurityManager service, running without principals"); + } + } + + const JSSecurityCallbacks* scb = JS_GetSecurityCallbacks(cx); + MOZ_ASSERT(scb, "We are assuming that nsScriptSecurityManager::Init() has been run"); + shellSecurityCallbacks = *scb; + JS_SetSecurityCallbacks(cx, &shellSecurityCallbacks); + +#ifdef TEST_TranslateThis + nsCOMPtr + translator(new nsXPCFunctionThisTranslator); + xpc->SetFunctionThisTranslator(NS_GET_IID(nsITestXPCFunctionCallback), translator); +#endif + + RefPtr backstagePass; + rv = NS_NewBackstagePass(getter_AddRefs(backstagePass)); + if (NS_FAILED(rv)) { + fprintf(gErrFile, "+++ Failed to create BackstagePass: %8x\n", + static_cast(rv)); + return 1; + } + + // Make the default XPCShell global use a fresh zone (rather than the + // System Zone) to improve cross-zone test coverage. + JS::CompartmentOptions options; + options.creationOptions().setZone(JS::FreshZone); + if (xpc::SharedMemoryEnabled()) + options.creationOptions().setSharedMemoryAndAtomicsEnabled(true); + options.behaviors().setVersion(JSVERSION_LATEST); + nsCOMPtr holder; + rv = xpc->InitClassesWithNewWrappedGlobal(cx, + static_cast(backstagePass), + systemprincipal, + 0, + options, + getter_AddRefs(holder)); + if (NS_FAILED(rv)) + return 1; + + // Initialize graphics prefs on the main thread, if not already done + gfxPrefs::GetSingleton(); + // Initialize e10s check on the main thread, if not already done + BrowserTabsRemoteAutostart(); +#ifdef XP_WIN + // Plugin may require audio session if installed plugin can initialize + // asynchronized. + AutoAudioSession audioSession; + +#if defined(MOZ_SANDBOX) + // Required for sandboxed child processes. + if (aShellData->sandboxBrokerServices) { + SandboxBroker::Initialize(aShellData->sandboxBrokerServices); + } else { + NS_WARNING("Failed to initialize broker services, sandboxed " + "processes will fail to start."); + } +#endif +#endif + + { + JS::Rooted glob(cx, holder->GetJSObject()); + if (!glob) { + return 1; + } + + // Even if we're building in a configuration where source is + // discarded, there's no reason to do that on XPCShell, and doing so + // might break various automation scripts. + JS::CompartmentBehaviorsRef(glob).setDiscardSource(false); + + backstagePass->SetGlobalObject(glob); + + JSAutoCompartment ac(cx, glob); + + if (!JS_InitReflectParse(cx, glob)) { + return 1; + } + + if (!JS_DefineFunctions(cx, glob, glob_functions) || + !JS_DefineProfilingFunctions(cx, glob)) { + return 1; + } + + JS::Rooted envobj(cx); + envobj = JS_DefineObject(cx, glob, "environment", &env_class); + if (!envobj) { + return 1; + } + + JS_SetPrivate(envobj, envp); + + nsAutoString workingDirectory; + if (GetCurrentWorkingDirectory(workingDirectory)) + gWorkingDirectory = &workingDirectory; + + JS_DefineProperty(cx, glob, "__LOCATION__", JS::UndefinedHandleValue, + JSPROP_SHARED, + GetLocationProperty, + nullptr); + + { + // We are almost certainly going to run script here, so we need an + // AutoEntryScript. This is Gecko-specific and not in any spec. + AutoEntryScript aes(backstagePass, "xpcshell argument processing"); + + // If an exception is thrown, we'll set our return code + // appropriately, and then let the AutoEntryScript destructor report + // the error to the console. + if (!ProcessArgs(aes, argv, argc, &dirprovider)) { + if (gExitCode) { + result = gExitCode; + } else if (gQuitting) { + result = 0; + } else { + result = EXITCODE_RUNTIME_ERROR; + } + } + } + + JS_DropPrincipals(cx, gJSPrincipals); + JS_SetAllNonReservedSlotsToUndefined(cx, glob); + JS_SetAllNonReservedSlotsToUndefined(cx, JS_GlobalLexicalEnvironment(glob)); + JS_GC(cx); + } + JS_GC(cx); + } // this scopes the nsCOMPtrs + + if (!XRE_ShutdownTestShell()) + NS_ERROR("problem shutting down testshell"); + + // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM + rv = NS_ShutdownXPCOM( nullptr ); + MOZ_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); + +#ifdef TEST_CALL_ON_WRAPPED_JS_AFTER_SHUTDOWN + // test of late call and release (see above) + JSContext* bogusCX; + bogus->Peek(&bogusCX); + bogus = nullptr; +#endif + + telStats = nullptr; + appDir = nullptr; + appFile = nullptr; + dirprovider.ClearGREDirs(); + dirprovider.ClearAppDir(); + dirprovider.ClearPluginDir(); + dirprovider.ClearAppFile(); + +#ifdef MOZ_CRASHREPORTER + // Shut down the crashreporter service to prevent leaking some strings it holds. + if (CrashReporter::GetEnabled()) + CrashReporter::UnsetExceptionHandler(); +#endif + + NS_LogTerm(); + + return result; +} + +void +XPCShellDirProvider::SetGREDirs(nsIFile* greDir) +{ + mGREDir = greDir; + mGREDir->Clone(getter_AddRefs(mGREBinDir)); +#ifdef XP_MACOSX + nsAutoCString leafName; + mGREDir->GetNativeLeafName(leafName); + if (leafName.Equals("Resources")) { + mGREBinDir->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS")); + } +#endif +} + +void +XPCShellDirProvider::SetAppFile(nsIFile* appFile) +{ + mAppFile = appFile; +} + +void +XPCShellDirProvider::SetAppDir(nsIFile* appDir) +{ + mAppDir = appDir; +} + +void +XPCShellDirProvider::SetPluginDir(nsIFile* pluginDir) +{ + mPluginDir = pluginDir; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +XPCShellDirProvider::AddRef() +{ + return 2; +} + +NS_IMETHODIMP_(MozExternalRefCountType) +XPCShellDirProvider::Release() +{ + return 1; +} + +NS_IMPL_QUERY_INTERFACE(XPCShellDirProvider, + nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +NS_IMETHODIMP +XPCShellDirProvider::GetFile(const char* prop, bool* persistent, + nsIFile* *result) +{ + if (mGREDir && !strcmp(prop, NS_GRE_DIR)) { + *persistent = true; + return mGREDir->Clone(result); + } else if (mGREBinDir && !strcmp(prop, NS_GRE_BIN_DIR)) { + *persistent = true; + return mGREBinDir->Clone(result); + } else if (mAppFile && !strcmp(prop, XRE_EXECUTABLE_FILE)) { + *persistent = true; + return mAppFile->Clone(result); + } else if (mGREDir && !strcmp(prop, NS_APP_PREF_DEFAULTS_50_DIR)) { + nsCOMPtr file; + *persistent = true; + if (NS_FAILED(mGREDir->Clone(getter_AddRefs(file))) || + NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("defaults"))) || + NS_FAILED(file->AppendNative(NS_LITERAL_CSTRING("pref")))) + return NS_ERROR_FAILURE; + file.forget(result); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +XPCShellDirProvider::GetFiles(const char* prop, nsISimpleEnumerator* *result) +{ + if (mGREDir && !strcmp(prop, "ChromeML")) { + nsCOMArray dirs; + + nsCOMPtr file; + mGREDir->Clone(getter_AddRefs(file)); + file->AppendNative(NS_LITERAL_CSTRING("chrome")); + dirs.AppendObject(file); + + nsresult rv = NS_GetSpecialDirectory(NS_APP_CHROME_DIR, + getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + dirs.AppendObject(file); + + return NS_NewArrayEnumerator(result, dirs); + } else if (!strcmp(prop, NS_APP_PREFS_DEFAULTS_DIR_LIST)) { + nsCOMArray dirs; + nsCOMPtr appDir; + bool exists; + if (mAppDir && + NS_SUCCEEDED(mAppDir->Clone(getter_AddRefs(appDir))) && + NS_SUCCEEDED(appDir->AppendNative(NS_LITERAL_CSTRING("defaults"))) && + NS_SUCCEEDED(appDir->AppendNative(NS_LITERAL_CSTRING("preferences"))) && + NS_SUCCEEDED(appDir->Exists(&exists)) && exists) { + dirs.AppendObject(appDir); + return NS_NewArrayEnumerator(result, dirs); + } + return NS_ERROR_FAILURE; + } else if (!strcmp(prop, NS_APP_PLUGINS_DIR_LIST)) { + nsCOMArray dirs; + // Add the test plugin location passed in by the caller or through + // runxpcshelltests. + if (mPluginDir) { + dirs.AppendObject(mPluginDir); + // If there was no path specified, default to the one set up by automation + } else { + nsCOMPtr file; + bool exists; + // We have to add this path, buildbot copies the test plugin directory + // to (app)/bin when unpacking test zips. + if (mGREDir) { + mGREDir->Clone(getter_AddRefs(file)); + if (NS_SUCCEEDED(mGREDir->Clone(getter_AddRefs(file)))) { + file->AppendNative(NS_LITERAL_CSTRING("plugins")); + if (NS_SUCCEEDED(file->Exists(&exists)) && exists) { + dirs.AppendObject(file); + } + } + } + } + return NS_NewArrayEnumerator(result, dirs); + } + return NS_ERROR_FAILURE; +} diff --git a/js/xpconnect/src/XPCString.cpp b/js/xpconnect/src/XPCString.cpp new file mode 100644 index 000000000..42a57744e --- /dev/null +++ b/js/xpconnect/src/XPCString.cpp @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * Infrastructure for sharing DOMString data with JSStrings. + * + * Importing an nsAString into JS: + * If possible (GetSharedBufferHandle works) use the external string support in + * JS to create a JSString that points to the readable's buffer. We keep a + * reference to the buffer handle until the JSString is finalized. + * + * Exporting a JSString as an nsAReadable: + * Wrap the JSString with a root-holding XPCJSReadableStringWrapper, which roots + * the string and exposes its buffer via the nsAString interface, as + * well as providing refcounting support. + */ + +#include "nsAutoPtr.h" +#include "nscore.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "jsapi.h" +#include "xpcpublic.h" + +using namespace JS; + +// static +void +XPCStringConvert::FreeZoneCache(JS::Zone* zone) +{ + // Put the zone user data into an AutoPtr (which will do the cleanup for us), + // and null out the user data (which may already be null). + nsAutoPtr cache(static_cast(JS_GetZoneUserData(zone))); + JS_SetZoneUserData(zone, nullptr); +} + +// static +void +XPCStringConvert::ClearZoneCache(JS::Zone* zone) +{ + // Although we clear the cache in FinalizeDOMString if needed, we also clear + // the cache here to avoid a dangling JSString* pointer when compacting GC + // moves the external string in memory. + + ZoneStringCache* cache = static_cast(JS_GetZoneUserData(zone)); + if (cache) { + cache->mBuffer = nullptr; + cache->mLength = 0; + cache->mString = nullptr; + } +} + +// static +void +XPCStringConvert::FinalizeLiteral(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars) +{ +} + +const JSStringFinalizer XPCStringConvert::sLiteralFinalizer = + { XPCStringConvert::FinalizeLiteral }; + +// static +void +XPCStringConvert::FinalizeDOMString(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars) +{ + nsStringBuffer* buf = nsStringBuffer::FromData(chars); + + // Clear the ZoneStringCache if needed, as this can be called outside GC + // when flattening an external string. + ZoneStringCache* cache = static_cast(JS_GetZoneUserData(zone)); + if (cache && cache->mBuffer == buf) { + cache->mBuffer = nullptr; + cache->mLength = 0; + cache->mString = nullptr; + } + + buf->Release(); +} + +const JSStringFinalizer XPCStringConvert::sDOMStringFinalizer = + { XPCStringConvert::FinalizeDOMString }; + +// convert a readable to a JSString, copying string data +// static +bool +XPCStringConvert::ReadableToJSVal(JSContext* cx, + const nsAString& readable, + nsStringBuffer** sharedBuffer, + MutableHandleValue vp) +{ + *sharedBuffer = nullptr; + + uint32_t length = readable.Length(); + + if (readable.IsLiteral()) { + JSString* str = JS_NewExternalString(cx, + static_cast(readable.BeginReading()), + length, &sLiteralFinalizer); + if (!str) + return false; + vp.setString(str); + return true; + } + + nsStringBuffer* buf = nsStringBuffer::FromString(readable); + if (buf) { + bool shared; + if (!StringBufferToJSVal(cx, buf, length, vp, &shared)) + return false; + if (shared) + *sharedBuffer = buf; + return true; + } + + // blech, have to copy. + JSString* str = JS_NewUCStringCopyN(cx, readable.BeginReading(), length); + if (!str) + return false; + vp.setString(str); + return true; +} + +namespace xpc { + +bool +NonVoidStringToJsval(JSContext* cx, nsAString& str, MutableHandleValue rval) +{ + nsStringBuffer* sharedBuffer; + if (!XPCStringConvert::ReadableToJSVal(cx, str, &sharedBuffer, rval)) + return false; + + if (sharedBuffer) { + // The string was shared but ReadableToJSVal didn't addref it. + // Move the ownership from str to jsstr. + str.ForgetSharedBuffer(); + } + return true; +} + +} // namespace xpc diff --git a/js/xpconnect/src/XPCThrower.cpp b/js/xpconnect/src/XPCThrower.cpp new file mode 100644 index 000000000..4a37e1554 --- /dev/null +++ b/js/xpconnect/src/XPCThrower.cpp @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Code for throwing errors into JavaScript. */ + +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "jsprf.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/Exceptions.h" +#include "nsStringGlue.h" + +using namespace mozilla; +using namespace mozilla::dom; + +bool XPCThrower::sVerbose = true; + +// static +void +XPCThrower::Throw(nsresult rv, JSContext* cx) +{ + const char* format; + if (JS_IsExceptionPending(cx)) + return; + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format)) + format = ""; + dom::Throw(cx, rv, nsDependentCString(format)); +} + +namespace xpc { + +bool +Throw(JSContext* cx, nsresult rv) +{ + XPCThrower::Throw(rv, cx); + return false; +} + +} // namespace xpc + +/* + * If there has already been an exception thrown, see if we're throwing the + * same sort of exception, and if we are, don't clobber the old one. ccx + * should be the current call context. + */ +// static +bool +XPCThrower::CheckForPendingException(nsresult result, JSContext* cx) +{ + nsCOMPtr e = XPCJSContext::Get()->GetPendingException(); + if (!e) + return false; + XPCJSContext::Get()->SetPendingException(nullptr); + + nsresult e_result; + if (NS_FAILED(e->GetResult(&e_result)) || e_result != result) + return false; + + ThrowExceptionObject(cx, e); + return true; +} + +// static +void +XPCThrower::Throw(nsresult rv, XPCCallContext& ccx) +{ + char* sz; + const char* format; + + if (CheckForPendingException(rv, ccx)) + return; + + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format)) + format = ""; + + sz = (char*) format; + NS_ENSURE_TRUE_VOID(sz); + + if (sz && sVerbose) + Verbosify(ccx, &sz, false); + + dom::Throw(ccx, rv, nsDependentCString(sz)); + + if (sz && sz != format) + JS_smprintf_free(sz); +} + + +// static +void +XPCThrower::ThrowBadResult(nsresult rv, nsresult result, XPCCallContext& ccx) +{ + char* sz; + const char* format; + const char* name; + + /* + * If there is a pending exception when the native call returns and + * it has the same error result as returned by the native call, then + * the native call may be passing through an error from a previous JS + * call. So we'll just throw that exception into our JS. Note that + * we don't need to worry about NS_ERROR_UNCATCHABLE_EXCEPTION, + * because presumably there would be no pending exception for that + * nsresult! + */ + + if (CheckForPendingException(result, ccx)) + return; + + // else... + + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format) || !format) + format = ""; + + if (nsXPCException::NameAndFormatForNSResult(result, &name, nullptr) && name) + sz = JS_smprintf("%s 0x%x (%s)", format, (unsigned) result, name); + else + sz = JS_smprintf("%s 0x%x", format, (unsigned) result); + NS_ENSURE_TRUE_VOID(sz); + + if (sz && sVerbose) + Verbosify(ccx, &sz, true); + + dom::Throw(ccx, result, nsDependentCString(sz)); + + if (sz) + JS_smprintf_free(sz); +} + +// static +void +XPCThrower::ThrowBadParam(nsresult rv, unsigned paramNum, XPCCallContext& ccx) +{ + char* sz; + const char* format; + + if (!nsXPCException::NameAndFormatForNSResult(rv, nullptr, &format)) + format = ""; + + sz = JS_smprintf("%s arg %d", format, paramNum); + NS_ENSURE_TRUE_VOID(sz); + + if (sz && sVerbose) + Verbosify(ccx, &sz, true); + + dom::Throw(ccx, rv, nsDependentCString(sz)); + + if (sz) + JS_smprintf_free(sz); +} + + +// static +void +XPCThrower::Verbosify(XPCCallContext& ccx, + char** psz, bool own) +{ + char* sz = nullptr; + + if (ccx.HasInterfaceAndMember()) { + XPCNativeInterface* iface = ccx.GetInterface(); + jsid id = ccx.GetMember()->GetName(); + JSAutoByteString bytes; + const char* name = JSID_IS_VOID(id) ? "Unknown" : bytes.encodeLatin1(ccx, JSID_TO_STRING(id)); + if (!name) { + name = ""; + } + sz = JS_smprintf("%s [%s.%s]", *psz, iface->GetNameString(), name); + } + + if (sz) { + if (own) + JS_smprintf_free(*psz); + *psz = sz; + } +} diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp new file mode 100644 index 000000000..a3d2b88c5 --- /dev/null +++ b/js/xpconnect/src/XPCVariant.cpp @@ -0,0 +1,800 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* nsIVariant implementation for xpconnect. */ + +#include "mozilla/Range.h" + +#include "xpcprivate.h" + +#include "jsfriendapi.h" +#include "jsprf.h" +#include "jswrapper.h" + +using namespace JS; +using namespace mozilla; + +NS_IMPL_CLASSINFO(XPCVariant, nullptr, 0, XPCVARIANT_CID) +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPCVariant) + NS_INTERFACE_MAP_ENTRY(XPCVariant) + NS_INTERFACE_MAP_ENTRY(nsIVariant) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_IMPL_QUERY_CLASSINFO(XPCVariant) +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER(XPCVariant, XPCVariant, nsIVariant) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCVariant) +NS_IMPL_CYCLE_COLLECTING_RELEASE(XPCVariant) + +XPCVariant::XPCVariant(JSContext* cx, const Value& aJSVal) + : mJSVal(aJSVal), mCCGeneration(0) +{ + if (!mJSVal.isPrimitive()) { + // XXXbholley - The innerization here was from bug 638026. Blake says + // the basic problem was that we were storing the C++ inner but the JS + // outer, which meant that, after navigation, the JS inner could be + // collected, which would cause us to try to recreate the JS inner at + // some later point after teardown, which would crash. This is shouldn't + // be a problem anymore because SetParentToWindow will do the right + // thing, but I'm saving the cleanup here for another day. Blake thinks + // that we should just not store the WN if we're creating a variant for + // an outer window. + JSObject* obj = js::ToWindowIfWindowProxy(&mJSVal.toObject()); + mJSVal = JS::ObjectValue(*obj); + + JSObject* unwrapped = js::CheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + mReturnRawObject = !(unwrapped && IS_WN_REFLECTOR(unwrapped)); + } else + mReturnRawObject = false; +} + +XPCTraceableVariant::~XPCTraceableVariant() +{ + Value val = GetJSValPreserveColor(); + + MOZ_ASSERT(val.isGCThing(), "Must be traceable or unlinked"); + + mData.Cleanup(); + + if (!val.isNull()) + RemoveFromRootSet(); +} + +void XPCTraceableVariant::TraceJS(JSTracer* trc) +{ + MOZ_ASSERT(GetJSValPreserveColor().isMarkable()); + JS::TraceEdge(trc, &mJSVal, "XPCTraceableVariant::mJSVal"); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(XPCVariant) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPCVariant) + JS::Value val = tmp->GetJSValPreserveColor(); + if (val.isObject()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mJSVal"); + cb.NoteJSChild(JS::GCCellPtr(val)); + } + + tmp->mData.Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPCVariant) + JS::Value val = tmp->GetJSValPreserveColor(); + + tmp->mData.Cleanup(); + + if (val.isMarkable()) { + XPCTraceableVariant* v = static_cast(tmp); + v->RemoveFromRootSet(); + } + tmp->mJSVal = JS::NullValue(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +// static +already_AddRefed +XPCVariant::newVariant(JSContext* cx, const Value& aJSVal) +{ + RefPtr variant; + + if (!aJSVal.isMarkable()) + variant = new XPCVariant(cx, aJSVal); + else + variant = new XPCTraceableVariant(cx, aJSVal); + + if (!variant->InitializeData(cx)) + return nullptr; + + return variant.forget(); +} + +// Helper class to give us a namespace for the table based code below. +class XPCArrayHomogenizer +{ +private: + enum Type + { + tNull = 0 , // null value + tInt , // Integer + tDbl , // Double + tBool , // Boolean + tStr , // String + tID , // ID + tArr , // Array + tISup , // nsISupports (really just a plain JSObject) + tUnk , // Unknown. Used only for initial state. + + tTypeCount , // Just a count for table dimensioning. + + tVar , // nsVariant - last ditch if no other common type found. + tErr // No valid state or type has this value. + }; + + // Table has tUnk as a state (column) but not as a type (row). + static const Type StateTable[tTypeCount][tTypeCount-1]; + +public: + static bool GetTypeForArray(JSContext* cx, HandleObject array, + uint32_t length, + nsXPTType* resultType, nsID* resultID); +}; + + +// Current state is the column down the side. +// Current type is the row along the top. +// New state is in the box at the intersection. + +const XPCArrayHomogenizer::Type +XPCArrayHomogenizer::StateTable[tTypeCount][tTypeCount-1] = { +/* tNull,tInt ,tDbl ,tBool,tStr ,tID ,tArr ,tISup */ +/* tNull */{tNull,tVar ,tVar ,tVar ,tStr ,tID ,tVar ,tISup }, +/* tInt */{tVar ,tInt ,tDbl ,tVar ,tVar ,tVar ,tVar ,tVar }, +/* tDbl */{tVar ,tDbl ,tDbl ,tVar ,tVar ,tVar ,tVar ,tVar }, +/* tBool */{tVar ,tVar ,tVar ,tBool,tVar ,tVar ,tVar ,tVar }, +/* tStr */{tStr ,tVar ,tVar ,tVar ,tStr ,tVar ,tVar ,tVar }, +/* tID */{tID ,tVar ,tVar ,tVar ,tVar ,tID ,tVar ,tVar }, +/* tArr */{tErr ,tErr ,tErr ,tErr ,tErr ,tErr ,tErr ,tErr }, +/* tISup */{tISup,tVar ,tVar ,tVar ,tVar ,tVar ,tVar ,tISup }, +/* tUnk */{tNull,tInt ,tDbl ,tBool,tStr ,tID ,tVar ,tISup }}; + +// static +bool +XPCArrayHomogenizer::GetTypeForArray(JSContext* cx, HandleObject array, + uint32_t length, + nsXPTType* resultType, nsID* resultID) +{ + Type state = tUnk; + Type type; + + RootedValue val(cx); + RootedObject jsobj(cx); + for (uint32_t i = 0; i < length; i++) { + if (!JS_GetElement(cx, array, i, &val)) + return false; + + if (val.isInt32()) { + type = tInt; + } else if (val.isDouble()) { + type = tDbl; + } else if (val.isBoolean()) { + type = tBool; + } else if (val.isUndefined() || val.isSymbol()) { + state = tVar; + break; + } else if (val.isNull()) { + type = tNull; + } else if (val.isString()) { + type = tStr; + } else { + MOZ_ASSERT(val.isObject(), "invalid type of jsval!"); + jsobj = &val.toObject(); + + bool isArray; + if (!JS_IsArrayObject(cx, jsobj, &isArray)) + return false; + + if (isArray) + type = tArr; + else if (xpc_JSObjectIsID(cx, jsobj)) + type = tID; + else + type = tISup; + } + + MOZ_ASSERT(state != tErr, "bad state table!"); + MOZ_ASSERT(type != tErr, "bad type!"); + MOZ_ASSERT(type != tVar, "bad type!"); + MOZ_ASSERT(type != tUnk, "bad type!"); + + state = StateTable[state][type]; + + MOZ_ASSERT(state != tErr, "bad state table!"); + MOZ_ASSERT(state != tUnk, "bad state table!"); + + if (state == tVar) + break; + } + + switch (state) { + case tInt : + *resultType = nsXPTType((uint8_t)TD_INT32); + break; + case tDbl : + *resultType = nsXPTType((uint8_t)TD_DOUBLE); + break; + case tBool: + *resultType = nsXPTType((uint8_t)TD_BOOL); + break; + case tStr : + *resultType = nsXPTType((uint8_t)TD_PWSTRING); + break; + case tID : + *resultType = nsXPTType((uint8_t)TD_PNSIID); + break; + case tISup: + *resultType = nsXPTType((uint8_t)TD_INTERFACE_IS_TYPE); + *resultID = NS_GET_IID(nsISupports); + break; + case tNull: + // FALL THROUGH + case tVar : + *resultType = nsXPTType((uint8_t)TD_INTERFACE_IS_TYPE); + *resultID = NS_GET_IID(nsIVariant); + break; + case tArr : + // FALL THROUGH + case tUnk : + // FALL THROUGH + case tErr : + // FALL THROUGH + default: + NS_ERROR("bad state"); + return false; + } + return true; +} + +bool XPCVariant::InitializeData(JSContext* cx) +{ + JS_CHECK_RECURSION(cx, return false); + + RootedValue val(cx, GetJSVal()); + + if (val.isInt32()) { + mData.SetFromInt32(val.toInt32()); + return true; + } + if (val.isDouble()) { + mData.SetFromDouble(val.toDouble()); + return true; + } + if (val.isBoolean()) { + mData.SetFromBool(val.toBoolean()); + return true; + } + // We can't represent symbol on C++ side, so pretend it is void. + if (val.isUndefined() || val.isSymbol()) { + mData.SetToVoid(); + return true; + } + if (val.isNull()) { + mData.SetToEmpty(); + return true; + } + if (val.isString()) { + JSString* str = val.toString(); + if (!str) + return false; + + MOZ_ASSERT(mData.GetType() == nsIDataType::VTYPE_EMPTY, + "Why do we already have data?"); + + size_t length = JS_GetStringLength(str); + mData.AllocateWStringWithSize(length); + + mozilla::Range destChars(mData.u.wstr.mWStringValue, length); + if (!JS_CopyStringChars(cx, destChars, str)) + return false; + + MOZ_ASSERT(mData.u.wstr.mWStringValue[length] == '\0'); + return true; + } + + // leaving only JSObject... + MOZ_ASSERT(val.isObject(), "invalid type of jsval!"); + + RootedObject jsobj(cx, &val.toObject()); + + // Let's see if it is a xpcJSID. + + const nsID* id = xpc_JSObjectToID(cx, jsobj); + if (id) { + mData.SetFromID(*id); + return true; + } + + // Let's see if it is a js array object. + + uint32_t len; + + bool isArray; + if (!JS_IsArrayObject(cx, jsobj, &isArray) || + (isArray && !JS_GetArrayLength(cx, jsobj, &len))) + { + return false; + } + + if (isArray) { + if (!len) { + // Zero length array + mData.SetToEmptyArray(); + return true; + } + + nsXPTType type; + nsID id; + + if (!XPCArrayHomogenizer::GetTypeForArray(cx, jsobj, len, &type, &id)) + return false; + + if (!XPCConvert::JSArray2Native(&mData.u.array.mArrayValue, + val, len, type, &id, nullptr)) + return false; + + mData.mType = nsIDataType::VTYPE_ARRAY; + if (type.IsInterfacePointer()) + mData.u.array.mArrayInterfaceID = id; + mData.u.array.mArrayCount = len; + mData.u.array.mArrayType = type.TagPart(); + + return true; + } + + // XXX This could be smarter and pick some more interesting iface. + + nsXPConnect* xpc = nsXPConnect::XPConnect(); + nsCOMPtr wrapper; + const nsIID& iid = NS_GET_IID(nsISupports); + + if (NS_FAILED(xpc->WrapJS(cx, jsobj, iid, getter_AddRefs(wrapper)))) { + return false; + } + + mData.SetFromInterface(iid, wrapper); + return true; +} + +NS_IMETHODIMP +XPCVariant::GetAsJSVal(MutableHandleValue result) +{ + result.set(GetJSVal()); + return NS_OK; +} + +// static +bool +XPCVariant::VariantDataToJS(nsIVariant* variant, + nsresult* pErr, MutableHandleValue pJSVal) +{ + // Get the type early because we might need to spoof it below. + uint16_t type; + if (NS_FAILED(variant->GetDataType(&type))) + return false; + + AutoJSContext cx; + RootedValue realVal(cx); + nsresult rv = variant->GetAsJSVal(&realVal); + + if (NS_SUCCEEDED(rv) && + (realVal.isPrimitive() || + type == nsIDataType::VTYPE_ARRAY || + type == nsIDataType::VTYPE_EMPTY_ARRAY || + type == nsIDataType::VTYPE_ID)) { + if (!JS_WrapValue(cx, &realVal)) + return false; + pJSVal.set(realVal); + return true; + } + + nsCOMPtr xpcvariant = do_QueryInterface(variant); + if (xpcvariant && xpcvariant->mReturnRawObject) { + MOZ_ASSERT(type == nsIDataType::VTYPE_INTERFACE || + type == nsIDataType::VTYPE_INTERFACE_IS, + "Weird variant"); + + if (!JS_WrapValue(cx, &realVal)) + return false; + pJSVal.set(realVal); + return true; + } + + // else, it's an object and we really need to double wrap it if we've + // already decided that its 'natural' type is as some sort of interface. + + // We just fall through to the code below and let it do what it does. + + // The nsIVariant is not a XPCVariant (or we act like it isn't). + // So we extract the data and do the Right Thing. + + // We ASSUME that the variant implementation can do these conversions... + + nsID iid; + + switch (type) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + { + double d; + if (NS_FAILED(variant->GetAsDouble(&d))) + return false; + pJSVal.setNumber(d); + return true; + } + case nsIDataType::VTYPE_BOOL: + { + bool b; + if (NS_FAILED(variant->GetAsBool(&b))) + return false; + pJSVal.setBoolean(b); + return true; + } + case nsIDataType::VTYPE_CHAR: + { + char c; + if (NS_FAILED(variant->GetAsChar(&c))) + return false; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&c, TD_CHAR, &iid, pErr); + } + case nsIDataType::VTYPE_WCHAR: + { + char16_t wc; + if (NS_FAILED(variant->GetAsWChar(&wc))) + return false; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&wc, TD_WCHAR, &iid, pErr); + } + case nsIDataType::VTYPE_ID: + { + if (NS_FAILED(variant->GetAsID(&iid))) + return false; + nsID* v = &iid; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&v, TD_PNSIID, &iid, pErr); + } + case nsIDataType::VTYPE_ASTRING: + { + nsAutoString astring; + if (NS_FAILED(variant->GetAsAString(astring))) + return false; + nsAutoString* v = &astring; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&v, TD_ASTRING, &iid, pErr); + } + case nsIDataType::VTYPE_DOMSTRING: + { + nsAutoString astring; + if (NS_FAILED(variant->GetAsAString(astring))) + return false; + nsAutoString* v = &astring; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&v, + TD_DOMSTRING, &iid, pErr); + } + case nsIDataType::VTYPE_CSTRING: + { + nsAutoCString cString; + if (NS_FAILED(variant->GetAsACString(cString))) + return false; + nsAutoCString* v = &cString; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&v, + TD_CSTRING, &iid, pErr); + } + case nsIDataType::VTYPE_UTF8STRING: + { + nsUTF8String utf8String; + if (NS_FAILED(variant->GetAsAUTF8String(utf8String))) + return false; + nsUTF8String* v = &utf8String; + return XPCConvert::NativeData2JS(pJSVal, (const void*)&v, + TD_UTF8STRING, &iid, pErr); + } + case nsIDataType::VTYPE_CHAR_STR: + { + char* pc; + if (NS_FAILED(variant->GetAsString(&pc))) + return false; + bool success = XPCConvert::NativeData2JS(pJSVal, (const void*)&pc, + TD_PSTRING, &iid, pErr); + free(pc); + return success; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: + { + char* pc; + uint32_t size; + if (NS_FAILED(variant->GetAsStringWithSize(&size, &pc))) + return false; + bool success = XPCConvert::NativeStringWithSize2JS(pJSVal, (const void*)&pc, + TD_PSTRING_SIZE_IS, size, pErr); + free(pc); + return success; + } + case nsIDataType::VTYPE_WCHAR_STR: + { + char16_t* pwc; + if (NS_FAILED(variant->GetAsWString(&pwc))) + return false; + bool success = XPCConvert::NativeData2JS(pJSVal, (const void*)&pwc, + TD_PSTRING, &iid, pErr); + free(pwc); + return success; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + { + char16_t* pwc; + uint32_t size; + if (NS_FAILED(variant->GetAsWStringWithSize(&size, &pwc))) + return false; + bool success = XPCConvert::NativeStringWithSize2JS(pJSVal, (const void*)&pwc, + TD_PWSTRING_SIZE_IS, size, pErr); + free(pwc); + return success; + } + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + { + nsISupports* pi; + nsID* piid; + if (NS_FAILED(variant->GetAsInterface(&piid, (void**)&pi))) + return false; + + iid = *piid; + free((char*)piid); + + bool success = XPCConvert::NativeData2JS(pJSVal, (const void*)&pi, + TD_INTERFACE_IS_TYPE, &iid, pErr); + if (pi) + pi->Release(); + return success; + } + case nsIDataType::VTYPE_ARRAY: + { + nsDiscriminatedUnion du; + nsresult rv; + + rv = variant->GetAsArray(&du.u.array.mArrayType, + &du.u.array.mArrayInterfaceID, + &du.u.array.mArrayCount, + &du.u.array.mArrayValue); + if (NS_FAILED(rv)) + return false; + + // must exit via VARIANT_DONE from here on... + du.mType = nsIDataType::VTYPE_ARRAY; + + nsXPTType conversionType; + uint16_t elementType = du.u.array.mArrayType; + const nsID* pid = nullptr; + + switch (elementType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + conversionType = nsXPTType((uint8_t)elementType); + break; + + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + conversionType = nsXPTType((uint8_t)elementType); + break; + + case nsIDataType::VTYPE_INTERFACE: + pid = &NS_GET_IID(nsISupports); + conversionType = nsXPTType((uint8_t)elementType); + break; + + case nsIDataType::VTYPE_INTERFACE_IS: + pid = &du.u.array.mArrayInterfaceID; + conversionType = nsXPTType((uint8_t)elementType); + break; + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_DOMSTRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + return false; + } + + bool success = + XPCConvert::NativeArray2JS(pJSVal, + (const void**)&du.u.array.mArrayValue, + conversionType, pid, + du.u.array.mArrayCount, pErr); + + return success; + } + case nsIDataType::VTYPE_EMPTY_ARRAY: + { + JSObject* array = JS_NewArrayObject(cx, 0); + if (!array) + return false; + pJSVal.setObject(*array); + return true; + } + case nsIDataType::VTYPE_VOID: + pJSVal.setUndefined(); + return true; + case nsIDataType::VTYPE_EMPTY: + pJSVal.setNull(); + return true; + default: + NS_ERROR("bad type in variant!"); + return false; + } +} + +/***************************************************************************/ +/***************************************************************************/ +// XXX These default implementations need to be improved to allow for +// some more interesting conversions. + + +NS_IMETHODIMP XPCVariant::GetDataType(uint16_t* aDataType) +{ + *aDataType = mData.GetType(); + return NS_OK; +} + +NS_IMETHODIMP XPCVariant::GetAsInt8(uint8_t* _retval) +{ + return mData.ConvertToInt8(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInt16(int16_t* _retval) +{ + return mData.ConvertToInt16(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInt32(int32_t* _retval) +{ + return mData.ConvertToInt32(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInt64(int64_t* _retval) +{ + return mData.ConvertToInt64(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint8(uint8_t* _retval) +{ + return mData.ConvertToUint8(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint16(uint16_t* _retval) +{ + return mData.ConvertToUint16(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint32(uint32_t* _retval) +{ + return mData.ConvertToUint32(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsUint64(uint64_t* _retval) +{ + return mData.ConvertToUint64(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsFloat(float* _retval) +{ + return mData.ConvertToFloat(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsDouble(double* _retval) +{ + return mData.ConvertToDouble(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsBool(bool* _retval) +{ + return mData.ConvertToBool(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsChar(char* _retval) +{ + return mData.ConvertToChar(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsWChar(char16_t* _retval) +{ + return mData.ConvertToWChar(_retval); +} + +NS_IMETHODIMP_(nsresult) XPCVariant::GetAsID(nsID* retval) +{ + return mData.ConvertToID(retval); +} + +NS_IMETHODIMP XPCVariant::GetAsAString(nsAString & _retval) +{ + return mData.ConvertToAString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsDOMString(nsAString & _retval) +{ + // A DOMString maps to an AString internally, so we can re-use + // ConvertToAString here. + return mData.ConvertToAString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsACString(nsACString & _retval) +{ + return mData.ConvertToACString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsAUTF8String(nsAUTF8String & _retval) +{ + return mData.ConvertToAUTF8String(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsString(char** _retval) +{ + return mData.ConvertToString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsWString(char16_t** _retval) +{ + return mData.ConvertToWString(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsISupports(nsISupports** _retval) +{ + return mData.ConvertToISupports(_retval); +} + +NS_IMETHODIMP XPCVariant::GetAsInterface(nsIID** iid, void** iface) +{ + return mData.ConvertToInterface(iid, iface); +} + + +NS_IMETHODIMP_(nsresult) XPCVariant::GetAsArray(uint16_t* type, nsIID* iid, uint32_t* count, void * *ptr) +{ + return mData.ConvertToArray(type, iid, count, ptr); +} + +NS_IMETHODIMP XPCVariant::GetAsStringWithSize(uint32_t* size, char** str) +{ + return mData.ConvertToStringWithSize(size, str); +} + +NS_IMETHODIMP XPCVariant::GetAsWStringWithSize(uint32_t* size, char16_t** str) +{ + return mData.ConvertToWStringWithSize(size, str); +} diff --git a/js/xpconnect/src/XPCWrappedJS.cpp b/js/xpconnect/src/XPCWrappedJS.cpp new file mode 100644 index 000000000..ebcfe6590 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedJS.cpp @@ -0,0 +1,731 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Class that wraps JS objects to appear as XPCOM objects. */ + +#include "xpcprivate.h" +#include "jsprf.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/Sprintf.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "nsCCUncollectableMarker.h" +#include "nsContentUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +// NOTE: much of the fancy footwork is done in xpcstubs.cpp + + +// nsXPCWrappedJS lifetime. +// +// An nsXPCWrappedJS is either rooting its JS object or is subject to finalization. +// The subject-to-finalization state lets wrappers support +// nsSupportsWeakReference in the case where the underlying JS object +// is strongly owned, but the wrapper itself is only weakly owned. +// +// A wrapper is rooting its JS object whenever its refcount is greater than 1. In +// this state, root wrappers are always added to the cycle collector graph. The +// wrapper keeps around an extra refcount, added in the constructor, to support +// the possibility of an eventual transition to the subject-to-finalization state. +// This extra refcount is ignored by the cycle collector, which traverses the "self" +// edge for this refcount. +// +// When the refcount of a rooting wrapper drops to 1, if there is no weak reference +// to the wrapper (which can only happen for the root wrapper), it is immediately +// Destroy()'d. Otherwise, it becomes subject to finalization. +// +// When a wrapper is subject to finalization, the wrapper has a refcount of 1. It is +// now owned exclusively by its JS object. Either a weak reference will be turned into +// a strong ref which will bring its refcount up to 2 and change the wrapper back to +// the rooting state, or it will stay alive until the JS object dies. If the JS object +// dies, then when XPCJSContext::FinalizeCallback calls FindDyingJSObjects +// it will find the wrapper and call Release() in it, destroying the wrapper. +// Otherwise, the wrapper will stay alive, even if it no longer has a weak reference +// to it. +// +// When the wrapper is subject to finalization, it is kept alive by an implicit reference +// from the JS object which is invisible to the cycle collector, so the cycle collector +// does not traverse any children of wrappers that are subject to finalization. This will +// result in a leak if a wrapper in the non-rooting state has an aggregated native that +// keeps alive the wrapper's JS object. See bug 947049. + + +// If traversing wrappedJS wouldn't release it, nor cause any other objects to be +// added to the graph, there is no need to add it to the graph at all. +bool +nsXPCWrappedJS::CanSkip() +{ + if (!nsCCUncollectableMarker::sGeneration) + return false; + + if (IsSubjectToFinalization()) + return true; + + // If this wrapper holds a gray object, need to trace it. + JSObject* obj = GetJSObjectPreserveColor(); + if (obj && JS::ObjectIsMarkedGray(obj)) + return false; + + // For non-root wrappers, check if the root wrapper will be + // added to the CC graph. + if (!IsRootWrapper()) { + // mRoot points to null after unlinking. + NS_ENSURE_TRUE(mRoot, false); + return mRoot->CanSkip(); + } + + // For the root wrapper, check if there is an aggregated + // native object that will be added to the CC graph. + if (!IsAggregatedToNative()) + return true; + + nsISupports* agg = GetAggregatedNativeObject(); + nsXPCOMCycleCollectionParticipant* cp = nullptr; + CallQueryInterface(agg, &cp); + nsISupports* canonical = nullptr; + agg->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast(&canonical)); + return cp && canonical && cp->CanSkipThis(canonical); +} + +NS_IMETHODIMP +NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Traverse + (void* p, nsCycleCollectionTraversalCallback& cb) +{ + nsISupports* s = static_cast(p); + MOZ_ASSERT(CheckForRightISupports(s), "not the nsISupports pointer we expect"); + nsXPCWrappedJS* tmp = Downcast(s); + + nsrefcnt refcnt = tmp->mRefCnt.get(); + if (cb.WantDebugInfo()) { + char name[72]; + if (tmp->GetClass()) + SprintfLiteral(name, "nsXPCWrappedJS (%s)", tmp->GetClass()->GetInterfaceName()); + else + SprintfLiteral(name, "nsXPCWrappedJS"); + cb.DescribeRefCountedNode(refcnt, name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsXPCWrappedJS, refcnt) + } + + // A wrapper that is subject to finalization will only die when its JS object dies. + if (tmp->IsSubjectToFinalization()) + return NS_OK; + + // Don't let the extra reference for nsSupportsWeakReference keep a wrapper that is + // not subject to finalization alive. + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "self"); + cb.NoteXPCOMChild(s); + + if (tmp->IsValid()) { + MOZ_ASSERT(refcnt > 1); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mJSObj"); + cb.NoteJSChild(JS::GCCellPtr(tmp->GetJSObjectPreserveColor())); + } + + if (tmp->IsRootWrapper()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "aggregated native"); + cb.NoteXPCOMChild(tmp->GetAggregatedNativeObject()); + } else { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "root"); + cb.NoteXPCOMChild(ToSupports(tmp->GetRootWrapper())); + } + + return NS_OK; +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXPCWrappedJS) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXPCWrappedJS) + tmp->Unlink(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +// XPCJSContext keeps a table of WJS, so we can remove them from +// the purple buffer in between CCs. +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsXPCWrappedJS) + return true; +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsXPCWrappedJS) + return tmp->CanSkip(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsXPCWrappedJS) + return tmp->CanSkip(); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +NS_IMETHODIMP +nsXPCWrappedJS::AggregatedQueryInterface(REFNSIID aIID, void** aInstancePtr) +{ + MOZ_ASSERT(IsAggregatedToNative(), "bad AggregatedQueryInterface call"); + *aInstancePtr = nullptr; + + if (!IsValid()) + return NS_ERROR_UNEXPECTED; + + // Put this here rather that in DelegatedQueryInterface because it needs + // to be in QueryInterface before the possible delegation to 'outer', but + // we don't want to do this check twice in one call in the normal case: + // once in QueryInterface and once in DelegatedQueryInterface. + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) { + NS_ADDREF(this); + *aInstancePtr = (void*) static_cast(this); + return NS_OK; + } + + return mClass->DelegatedQueryInterface(this, aIID, aInstancePtr); +} + +NS_IMETHODIMP +nsXPCWrappedJS::QueryInterface(REFNSIID aIID, void** aInstancePtr) +{ + if (nullptr == aInstancePtr) { + NS_PRECONDITION(false, "null pointer"); + return NS_ERROR_NULL_POINTER; + } + + *aInstancePtr = nullptr; + + if ( aIID.Equals(NS_GET_IID(nsXPCOMCycleCollectionParticipant)) ) { + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(nsXPCWrappedJS); + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsCycleCollectionISupports))) { + *aInstancePtr = + NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + return NS_OK; + } + + if (!IsValid()) + return NS_ERROR_UNEXPECTED; + + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJSUnmarkGray))) { + *aInstancePtr = nullptr; + + mJSObj.exposeToActiveJS(); + + // Just return some error value since one isn't supposed to use + // nsIXPConnectWrappedJSUnmarkGray objects for anything. + return NS_ERROR_FAILURE; + } + + // Always check for this first so that our 'outer' can get this interface + // from us without recurring into a call to the outer's QI! + if (aIID.Equals(NS_GET_IID(nsIXPConnectWrappedJS))) { + NS_ADDREF(this); + *aInstancePtr = (void*) static_cast(this); + return NS_OK; + } + + nsISupports* outer = GetAggregatedNativeObject(); + if (outer) + return outer->QueryInterface(aIID, aInstancePtr); + + // else... + + return mClass->DelegatedQueryInterface(this, aIID, aInstancePtr); +} + + +// For a description of nsXPCWrappedJS lifetime and reference counting, see +// the comment at the top of this file. + +MozExternalRefCountType +nsXPCWrappedJS::AddRef(void) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::AddRef called off main thread"); + + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + nsrefcnt cnt = mRefCnt.incr(base); + NS_LOG_ADDREF(this, cnt, "nsXPCWrappedJS", sizeof(*this)); + + if (2 == cnt && IsValid()) { + GetJSObject(); // Unmark gray JSObject. + mClass->GetContext()->AddWrappedJSRoot(this); + } + + return cnt; +} + +MozExternalRefCountType +nsXPCWrappedJS::Release(void) +{ + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::Release called off main thread"); + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + NS_ASSERT_OWNINGTHREAD(nsXPCWrappedJS); + + bool shouldDelete = false; + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrappedJS)::Upcast(this); + nsrefcnt cnt = mRefCnt.decr(base, &shouldDelete); + NS_LOG_RELEASE(this, cnt, "nsXPCWrappedJS"); + + if (0 == cnt) { + if (MOZ_UNLIKELY(shouldDelete)) { + mRefCnt.stabilizeForDeletion(); + DeleteCycleCollectable(); + } else { + mRefCnt.incr(base); + Destroy(); + mRefCnt.decr(base); + } + } else if (1 == cnt) { + if (IsValid()) + RemoveFromRootSet(); + + // If we are not a root wrapper being used from a weak reference, + // then the extra ref is not needed and we can let outselves be + // deleted. + if (!HasWeakReferences()) + return Release(); + + MOZ_ASSERT(IsRootWrapper(), "Only root wrappers should have weak references"); + } + return cnt; +} + +NS_IMETHODIMP_(void) +nsXPCWrappedJS::DeleteCycleCollectable(void) +{ + delete this; +} + +void +nsXPCWrappedJS::TraceJS(JSTracer* trc) +{ + MOZ_ASSERT(mRefCnt >= 2 && IsValid(), "must be strongly referenced"); + JS::TraceEdge(trc, &mJSObj, "nsXPCWrappedJS::mJSObj"); +} + +NS_IMETHODIMP +nsXPCWrappedJS::GetWeakReference(nsIWeakReference** aInstancePtr) +{ + if (!IsRootWrapper()) + return mRoot->GetWeakReference(aInstancePtr); + + return nsSupportsWeakReference::GetWeakReference(aInstancePtr); +} + +JSObject* +nsXPCWrappedJS::GetJSObject() +{ + return mJSObj; +} + +// static +nsresult +nsXPCWrappedJS::GetNewOrUsed(JS::HandleObject jsObj, + REFNSIID aIID, + nsXPCWrappedJS** wrapperResult) +{ + // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::GetNewOrUsed called off main thread"); + + AutoJSContext cx; + + bool allowNonScriptable = mozilla::jsipc::IsWrappedCPOW(jsObj); + RefPtr clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, aIID, + allowNonScriptable); + if (!clasp) + return NS_ERROR_FAILURE; + + JS::RootedObject rootJSObj(cx, clasp->GetRootJSObject(cx, jsObj)); + if (!rootJSObj) + return NS_ERROR_FAILURE; + + xpc::CompartmentPrivate* rootComp = xpc::CompartmentPrivate::Get(rootJSObj); + MOZ_ASSERT(rootComp); + + // Find any existing wrapper. + RefPtr root = rootComp->GetWrappedJSMap()->Find(rootJSObj); + MOZ_ASSERT_IF(root, !nsXPConnect::GetContextInstance()->GetMultiCompartmentWrappedJSMap()-> + Find(rootJSObj)); + if (!root) { + root = nsXPConnect::GetContextInstance()->GetMultiCompartmentWrappedJSMap()-> + Find(rootJSObj); + } + + nsresult rv = NS_ERROR_FAILURE; + if (root) { + RefPtr wrapper = root->FindOrFindInherited(aIID); + if (wrapper) { + wrapper.forget(wrapperResult); + return NS_OK; + } + } else if (rootJSObj != jsObj) { + + // Make a new root wrapper, because there is no existing + // root wrapper, and the wrapper we are trying to make isn't + // a root. + RefPtr rootClasp = + nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports)); + if (!rootClasp) + return NS_ERROR_FAILURE; + + root = new nsXPCWrappedJS(cx, rootJSObj, rootClasp, nullptr, &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + RefPtr wrapper = new nsXPCWrappedJS(cx, jsObj, clasp, root, &rv); + if (NS_FAILED(rv)) { + return rv; + } + wrapper.forget(wrapperResult); + return NS_OK; +} + +nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx, + JSObject* aJSObj, + nsXPCWrappedJSClass* aClass, + nsXPCWrappedJS* root, + nsresult* rv) + : mJSObj(aJSObj), + mClass(aClass), + mRoot(root ? root : this), + mNext(nullptr) +{ + *rv = InitStub(GetClass()->GetIID()); + // Continue even in the failure case, so that our refcounting/Destroy + // behavior works correctly. + + // There is an extra AddRef to support weak references to wrappers + // that are subject to finalization. See the top of the file for more + // details. + NS_ADDREF_THIS(); + + if (IsRootWrapper()) { + MOZ_ASSERT(!IsMultiCompartment(), "mNext is always nullptr here"); + if (!xpc::CompartmentPrivate::Get(mJSObj)->GetWrappedJSMap()->Add(cx, this)) { + *rv = NS_ERROR_OUT_OF_MEMORY; + } + } else { + NS_ADDREF(mRoot); + mNext = mRoot->mNext; + mRoot->mNext = this; + + // We always start wrappers in the per-compartment table. If adding + // this wrapper to the chain causes it to cross compartments, we need + // to migrate the chain to the global table on the XPCJSContext. + if (mRoot->IsMultiCompartment()) { + xpc::CompartmentPrivate::Get(mRoot->mJSObj)->GetWrappedJSMap()->Remove(mRoot); + auto destMap = nsXPConnect::GetContextInstance()->GetMultiCompartmentWrappedJSMap(); + if (!destMap->Add(cx, mRoot)) { + *rv = NS_ERROR_OUT_OF_MEMORY; + } + } + } +} + +nsXPCWrappedJS::~nsXPCWrappedJS() +{ + Destroy(); +} + +void +XPCJSContext::RemoveWrappedJS(nsXPCWrappedJS* wrapper) +{ + AssertInvalidWrappedJSNotInTable(wrapper); + if (!wrapper->IsValid()) + return; + + // It is possible for the same JS XPCOM implementation object to be wrapped + // with a different interface in multiple JSCompartments. In this case, the + // wrapper chain will contain references to multiple compartments. While we + // always store single-compartment chains in the per-compartment wrapped-js + // table, chains in the multi-compartment wrapped-js table may contain + // single-compartment chains, if they have ever contained a wrapper in a + // different compartment. Since removal requires a lookup anyway, we just do + // the remove on both tables unconditionally. + MOZ_ASSERT_IF(wrapper->IsMultiCompartment(), + !xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())-> + GetWrappedJSMap()->HasWrapper(wrapper)); + GetMultiCompartmentWrappedJSMap()->Remove(wrapper); + xpc::CompartmentPrivate::Get(wrapper->GetJSObjectPreserveColor())->GetWrappedJSMap()-> + Remove(wrapper); +} + +#ifdef DEBUG +static void +NotHasWrapperAssertionCallback(JSContext* cx, void* data, JSCompartment* comp) +{ + auto wrapper = static_cast(data); + auto xpcComp = xpc::CompartmentPrivate::Get(comp); + MOZ_ASSERT_IF(xpcComp, !xpcComp->GetWrappedJSMap()->HasWrapper(wrapper)); +} +#endif + +void +XPCJSContext::AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const +{ +#ifdef DEBUG + if (!wrapper->IsValid()) { + MOZ_ASSERT(!GetMultiCompartmentWrappedJSMap()->HasWrapper(wrapper)); + if (!mGCIsRunning) + JS_IterateCompartments(Context(), wrapper, NotHasWrapperAssertionCallback); + } +#endif +} + +void +nsXPCWrappedJS::Destroy() +{ + MOZ_ASSERT(1 == int32_t(mRefCnt), "should be stabilized for deletion"); + + if (IsRootWrapper()) + nsXPConnect::GetContextInstance()->RemoveWrappedJS(this); + Unlink(); +} + +void +nsXPCWrappedJS::Unlink() +{ + nsXPConnect::GetContextInstance()->AssertInvalidWrappedJSNotInTable(this); + + if (IsValid()) { + XPCJSContext* cx = nsXPConnect::GetContextInstance(); + if (cx) { + if (IsRootWrapper()) + cx->RemoveWrappedJS(this); + + if (mRefCnt > 1) + RemoveFromRootSet(); + } + + mJSObj = nullptr; + } + + if (IsRootWrapper()) { + ClearWeakReferences(); + } else if (mRoot) { + // unlink this wrapper + nsXPCWrappedJS* cur = mRoot; + while (1) { + if (cur->mNext == this) { + cur->mNext = mNext; + break; + } + cur = cur->mNext; + MOZ_ASSERT(cur, "failed to find wrapper in its own chain"); + } + + // Note: unlinking this wrapper may have changed us from a multi- + // compartment wrapper chain to a single-compartment wrapper chain. We + // leave the wrapper in the multi-compartment table as it is likely to + // need to be multi-compartment again in the future and, moreover, we + // cannot get a JSContext here. + + // let the root go + NS_RELEASE(mRoot); + } + + mClass = nullptr; + if (mOuter) { + XPCJSContext* cx = nsXPConnect::GetContextInstance(); + if (cx->GCIsRunning()) { + DeferredFinalize(mOuter.forget().take()); + } else { + mOuter = nullptr; + } + } +} + +bool +nsXPCWrappedJS::IsMultiCompartment() const +{ + MOZ_ASSERT(IsRootWrapper()); + JSCompartment* compartment = Compartment(); + nsXPCWrappedJS* next = mNext; + while (next) { + if (next->Compartment() != compartment) + return true; + next = next->mNext; + } + return false; +} + +nsXPCWrappedJS* +nsXPCWrappedJS::Find(REFNSIID aIID) +{ + if (aIID.Equals(NS_GET_IID(nsISupports))) + return mRoot; + + for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) { + if (aIID.Equals(cur->GetIID())) + return cur; + } + + return nullptr; +} + +// check if asking for an interface that some wrapper in the chain inherits from +nsXPCWrappedJS* +nsXPCWrappedJS::FindInherited(REFNSIID aIID) +{ + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports)), "bad call sequence"); + + for (nsXPCWrappedJS* cur = mRoot; cur; cur = cur->mNext) { + bool found; + if (NS_SUCCEEDED(cur->GetClass()->GetInterfaceInfo()-> + HasAncestor(&aIID, &found)) && found) + return cur; + } + + return nullptr; +} + +NS_IMETHODIMP +nsXPCWrappedJS::GetInterfaceInfo(nsIInterfaceInfo** infoResult) +{ + MOZ_ASSERT(GetClass(), "wrapper without class"); + MOZ_ASSERT(GetClass()->GetInterfaceInfo(), "wrapper class without interface"); + + // Since failing to get this info will crash some platforms(!), we keep + // mClass valid at shutdown time. + + nsCOMPtr info = GetClass()->GetInterfaceInfo(); + if (!info) + return NS_ERROR_UNEXPECTED; + info.forget(infoResult); + return NS_OK; +} + +NS_IMETHODIMP +nsXPCWrappedJS::CallMethod(uint16_t methodIndex, + const XPTMethodDescriptor* info, + nsXPTCMiniVariant* params) +{ + // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread(), + "nsXPCWrappedJS::CallMethod called off main thread"); + + if (!IsValid()) + return NS_ERROR_UNEXPECTED; + return GetClass()->CallMethod(this, methodIndex, info, params); +} + +NS_IMETHODIMP +nsXPCWrappedJS::GetInterfaceIID(nsIID** iid) +{ + NS_PRECONDITION(iid, "bad param"); + + *iid = (nsIID*) nsMemory::Clone(&(GetIID()), sizeof(nsIID)); + return *iid ? NS_OK : NS_ERROR_UNEXPECTED; +} + +void +nsXPCWrappedJS::SystemIsBeingShutDown() +{ + // XXX It turns out that it is better to leak here then to do any Releases + // and have them propagate into all sorts of mischief as the system is being + // shutdown. This was learned the hard way :( + + // mJSObj == nullptr is used to indicate that the wrapper is no longer valid + // and that calls should fail without trying to use any of the + // xpconnect mechanisms. 'IsValid' is implemented by checking this pointer. + + // NOTE: that mClass is retained so that GetInterfaceInfo can continue to + // work (and avoid crashing some platforms). + + // Use of unsafeGet() is to avoid triggering post barriers in shutdown, as + // this will access the chunk containing mJSObj, which may have been freed + // at this point. + *mJSObj.unsafeGet() = nullptr; + + // Notify other wrappers in the chain. + if (mNext) + mNext->SystemIsBeingShutDown(); +} + +size_t +nsXPCWrappedJS::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const +{ + // mJSObject is a JS pointer, so don't measure the object. + // mClass is not uniquely owned by this WrappedJS. Measure it in IID2WrappedJSClassMap. + // mRoot is not measured because it is either |this| or we have already measured it. + // mOuter is rare and probably not uniquely owned by this. + size_t n = mallocSizeOf(this); + n += nsAutoXPTCStub::SizeOfExcludingThis(mallocSizeOf); + + // Wrappers form a linked list via the mNext field, so include them all + // in the measurement. Only root wrappers are stored in the map, so + // everything will be measured exactly once. + if (mNext) + n += mNext->SizeOfIncludingThis(mallocSizeOf); + + return n; +} + +/***************************************************************************/ + +NS_IMETHODIMP +nsXPCWrappedJS::GetEnumerator(nsISimpleEnumerator * *aEnumerate) +{ + AutoJSContext cx; + XPCCallContext ccx(cx); + if (!ccx.IsValid()) + return NS_ERROR_UNEXPECTED; + + return nsXPCWrappedJSClass::BuildPropertyEnumerator(ccx, GetJSObject(), + aEnumerate); +} + +NS_IMETHODIMP +nsXPCWrappedJS::GetProperty(const nsAString & name, nsIVariant** _retval) +{ + AutoJSContext cx; + XPCCallContext ccx(cx); + if (!ccx.IsValid()) + return NS_ERROR_UNEXPECTED; + + return nsXPCWrappedJSClass:: + GetNamedPropertyAsVariant(ccx, GetJSObject(), name, _retval); +} + +/***************************************************************************/ + +NS_IMETHODIMP +nsXPCWrappedJS::DebugDump(int16_t depth) +{ +#ifdef DEBUG + XPC_LOG_ALWAYS(("nsXPCWrappedJS @ %x with mRefCnt = %d", this, mRefCnt.get())); + XPC_LOG_INDENT(); + + XPC_LOG_ALWAYS(("%s wrapper around JSObject @ %x", \ + IsRootWrapper() ? "ROOT":"non-root", mJSObj.get())); + char* name; + GetClass()->GetInterfaceInfo()->GetName(&name); + XPC_LOG_ALWAYS(("interface name is %s", name)); + if (name) + free(name); + char * iid = GetClass()->GetIID().ToString(); + XPC_LOG_ALWAYS(("IID number is %s", iid ? iid : "invalid")); + if (iid) + free(iid); + XPC_LOG_ALWAYS(("nsXPCWrappedJSClass @ %x", mClass.get())); + + if (!IsRootWrapper()) + XPC_LOG_OUTDENT(); + if (mNext) { + if (IsRootWrapper()) { + XPC_LOG_ALWAYS(("Additional wrappers for this object...")); + XPC_LOG_INDENT(); + } + mNext->DebugDump(depth); + if (IsRootWrapper()) + XPC_LOG_OUTDENT(); + } + if (IsRootWrapper()) + XPC_LOG_OUTDENT(); +#endif + return NS_OK; +} diff --git a/js/xpconnect/src/XPCWrappedJSClass.cpp b/js/xpconnect/src/XPCWrappedJSClass.cpp new file mode 100644 index 000000000..2c9fd66bc --- /dev/null +++ b/js/xpconnect/src/XPCWrappedJSClass.cpp @@ -0,0 +1,1457 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Sharable code and data for wrapper around JSObjects. */ + +#include "xpcprivate.h" +#include "jsprf.h" +#include "nsArrayEnumerator.h" +#include "nsContentUtils.h" +#include "nsWrapperCache.h" +#include "AccessCheck.h" +#include "nsJSUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMExceptionBinding.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" + +#include "jsapi.h" +#include "jsfriendapi.h" + +using namespace xpc; +using namespace JS; +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(nsXPCWrappedJSClass, nsIXPCWrappedJSClass) + +// the value of this variable is never used - we use its address as a sentinel +static uint32_t zero_methods_descriptor; + +bool AutoScriptEvaluate::StartEvaluating(HandleObject scope) +{ + NS_PRECONDITION(!mEvaluated, "AutoScriptEvaluate::Evaluate should only be called once"); + + if (!mJSContext) + return true; + + mEvaluated = true; + + JS_BeginRequest(mJSContext); + mAutoCompartment.emplace(mJSContext, scope); + + // Saving the exception state keeps us from interfering with another script + // that may also be running on this context. This occurred first with the + // js debugger, as described in + // http://bugzilla.mozilla.org/show_bug.cgi?id=88130 but presumably could + // show up in any situation where a script calls into a wrapped js component + // on the same context, while the context has a nonzero exception state. + mState.emplace(mJSContext); + + return true; +} + +AutoScriptEvaluate::~AutoScriptEvaluate() +{ + if (!mJSContext || !mEvaluated) + return; + mState->restore(); + + JS_EndRequest(mJSContext); +} + +// It turns out that some errors may be not worth reporting. So, this +// function is factored out to manage that. +bool xpc_IsReportableErrorCode(nsresult code) +{ + if (NS_SUCCEEDED(code)) + return false; + + switch (code) { + // Error codes that we don't want to report as errors... + // These generally indicate bad interface design AFAIC. + case NS_ERROR_FACTORY_REGISTER_AGAIN: + case NS_BASE_STREAM_WOULD_BLOCK: + return false; + default: + return true; + } +} + +// A little stack-based RAII class to help management of the XPCJSContext +// PendingResult. +class MOZ_STACK_CLASS AutoSavePendingResult { +public: + explicit AutoSavePendingResult(XPCJSContext* xpccx) : + mXPCContext(xpccx) + { + // Save any existing pending result and reset to NS_OK for this invocation. + mSavedResult = xpccx->GetPendingResult(); + xpccx->SetPendingResult(NS_OK); + } + ~AutoSavePendingResult() { + mXPCContext->SetPendingResult(mSavedResult); + } +private: + XPCJSContext* mXPCContext; + nsresult mSavedResult; +}; + +// static +already_AddRefed +nsXPCWrappedJSClass::GetNewOrUsed(JSContext* cx, REFNSIID aIID, bool allowNonScriptable) +{ + XPCJSContext* xpccx = nsXPConnect::GetContextInstance(); + IID2WrappedJSClassMap* map = xpccx->GetWrappedJSClassMap(); + RefPtr clasp = map->Find(aIID); + + if (!clasp) { + nsCOMPtr info; + nsXPConnect::XPConnect()->GetInfoForIID(&aIID, getter_AddRefs(info)); + if (info) { + bool canScript, isBuiltin; + if (NS_SUCCEEDED(info->IsScriptable(&canScript)) && (canScript || allowNonScriptable) && + NS_SUCCEEDED(info->IsBuiltinClass(&isBuiltin)) && !isBuiltin && + nsXPConnect::IsISupportsDescendant(info)) + { + clasp = new nsXPCWrappedJSClass(cx, aIID, info); + if (!clasp->mDescriptors) + clasp = nullptr; + } + } + } + return clasp.forget(); +} + +nsXPCWrappedJSClass::nsXPCWrappedJSClass(JSContext* cx, REFNSIID aIID, + nsIInterfaceInfo* aInfo) + : mContext(nsXPConnect::GetContextInstance()), + mInfo(aInfo), + mName(nullptr), + mIID(aIID), + mDescriptors(nullptr) +{ + mContext->GetWrappedJSClassMap()->Add(this); + + uint16_t methodCount; + if (NS_SUCCEEDED(mInfo->GetMethodCount(&methodCount))) { + if (methodCount) { + int wordCount = (methodCount/32)+1; + if (nullptr != (mDescriptors = new uint32_t[wordCount])) { + int i; + // init flags to 0; + for (i = wordCount-1; i >= 0; i--) + mDescriptors[i] = 0; + + for (i = 0; i < methodCount; i++) { + const nsXPTMethodInfo* info; + if (NS_SUCCEEDED(mInfo->GetMethodInfo(i, &info))) + SetReflectable(i, XPCConvert::IsMethodReflectable(*info)); + else { + delete [] mDescriptors; + mDescriptors = nullptr; + break; + } + } + } + } else { + mDescriptors = &zero_methods_descriptor; + } + } +} + +nsXPCWrappedJSClass::~nsXPCWrappedJSClass() +{ + if (mDescriptors && mDescriptors != &zero_methods_descriptor) + delete [] mDescriptors; + if (mContext) + mContext->GetWrappedJSClassMap()->Remove(this); + + if (mName) + free(mName); +} + +JSObject* +nsXPCWrappedJSClass::CallQueryInterfaceOnJSObject(JSContext* cx, + JSObject* jsobjArg, + REFNSIID aIID) +{ + RootedObject jsobj(cx, jsobjArg); + JSObject* id; + RootedValue retval(cx); + RootedObject retObj(cx); + bool success = false; + RootedValue fun(cx); + + // In bug 503926, we added a security check to make sure that we don't + // invoke content QI functions. In the modern world, this is probably + // unnecessary, because invoking QI involves passing an IID object to + // content, which will fail. But we do a belt-and-suspenders check to + // make sure that content can never trigger the rat's nest of code below. + // Once we completely turn off XPConnect for the web, this can definitely + // go away. + if (!AccessCheck::isChrome(jsobj) || + !AccessCheck::isChrome(js::UncheckedUnwrap(jsobj))) + { + return nullptr; + } + + // OK, it looks like we'll be calling into JS code. + AutoScriptEvaluate scriptEval(cx); + + // XXX we should install an error reporter that will send reports to + // the JS error console service. + if (!scriptEval.StartEvaluating(jsobj)) + return nullptr; + + // check upfront for the existence of the function property + HandleId funid = mContext->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE); + if (!JS_GetPropertyById(cx, jsobj, funid, &fun) || fun.isPrimitive()) + return nullptr; + + // Ensure that we are asking for a scriptable interface. + // NB: It's important for security that this check is here rather + // than later, since it prevents untrusted objects from implementing + // some interfaces in JS and aggregating a trusted object to + // implement intentionally (for security) unscriptable interfaces. + // We so often ask for nsISupports that we can short-circuit the test... + if (!aIID.Equals(NS_GET_IID(nsISupports))) { + bool allowNonScriptable = mozilla::jsipc::IsWrappedCPOW(jsobj); + + nsCOMPtr info; + nsXPConnect::XPConnect()->GetInfoForIID(&aIID, getter_AddRefs(info)); + if (!info) + return nullptr; + bool canScript, isBuiltin; + if (NS_FAILED(info->IsScriptable(&canScript)) || (!canScript && !allowNonScriptable) || + NS_FAILED(info->IsBuiltinClass(&isBuiltin)) || isBuiltin) + return nullptr; + } + + id = xpc_NewIDObject(cx, jsobj, aIID); + if (id) { + // Throwing NS_NOINTERFACE is the prescribed way to fail QI from JS. It + // is not an exception that is ever worth reporting, but we don't want + // to eat all exceptions either. + + { + RootedValue arg(cx, JS::ObjectValue(*id)); + success = JS_CallFunctionValue(cx, jsobj, fun, HandleValueArray(arg), &retval); + } + + if (!success && JS_IsExceptionPending(cx)) { + RootedValue jsexception(cx, NullValue()); + + if (JS_GetPendingException(cx, &jsexception)) { + nsresult rv; + if (jsexception.isObject()) { + // XPConnect may have constructed an object to represent a + // C++ QI failure. See if that is the case. + JS::Rooted exceptionObj(cx, &jsexception.toObject()); + Exception* e = nullptr; + UNWRAP_OBJECT(Exception, &exceptionObj, e); + + if (e && + NS_SUCCEEDED(e->GetResult(&rv)) && + rv == NS_NOINTERFACE) { + JS_ClearPendingException(cx); + } + } else if (jsexception.isNumber()) { + // JS often throws an nsresult. + if (jsexception.isDouble()) + // Visual Studio 9 doesn't allow casting directly from + // a double to an enumeration type, contrary to + // 5.2.9(10) of C++11, so add an intermediate cast. + rv = (nsresult)(uint32_t)(jsexception.toDouble()); + else + rv = (nsresult)(jsexception.toInt32()); + + if (rv == NS_NOINTERFACE) + JS_ClearPendingException(cx); + } + } + } else if (!success) { + NS_WARNING("QI hook ran OOMed - this is probably a bug!"); + } + } + + if (success) + success = JS_ValueToObject(cx, retval, &retObj); + + return success ? retObj.get() : nullptr; +} + +/***************************************************************************/ + +static bool +GetNamedPropertyAsVariantRaw(XPCCallContext& ccx, + HandleObject aJSObj, + HandleId aName, + nsIVariant** aResult, + nsresult* pErr) +{ + nsXPTType type = nsXPTType((uint8_t)TD_INTERFACE_TYPE); + RootedValue val(ccx); + + return JS_GetPropertyById(ccx, aJSObj, aName, &val) && + XPCConvert::JSData2Native(aResult, val, type, + &NS_GET_IID(nsIVariant), pErr); +} + +// static +nsresult +nsXPCWrappedJSClass::GetNamedPropertyAsVariant(XPCCallContext& ccx, + JSObject* aJSObjArg, + const nsAString& aName, + nsIVariant** aResult) +{ + JSContext* cx = ccx.GetJSContext(); + RootedObject aJSObj(cx, aJSObjArg); + + AutoScriptEvaluate scriptEval(cx); + if (!scriptEval.StartEvaluating(aJSObj)) + return NS_ERROR_FAILURE; + + // Wrap the string in a Value after the AutoScriptEvaluate, so that the + // resulting value ends up in the correct compartment. + nsStringBuffer* buf; + RootedValue value(cx); + if (!XPCStringConvert::ReadableToJSVal(ccx, aName, &buf, &value)) + return NS_ERROR_OUT_OF_MEMORY; + if (buf) + buf->AddRef(); + + RootedId id(cx); + nsresult rv = NS_OK; + if (!JS_ValueToId(cx, value, &id) || + !GetNamedPropertyAsVariantRaw(ccx, aJSObj, id, aResult, &rv)) { + if (NS_FAILED(rv)) + return rv; + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +/***************************************************************************/ + +// static +nsresult +nsXPCWrappedJSClass::BuildPropertyEnumerator(XPCCallContext& ccx, + JSObject* aJSObjArg, + nsISimpleEnumerator** aEnumerate) +{ + JSContext* cx = ccx.GetJSContext(); + RootedObject aJSObj(cx, aJSObjArg); + + AutoScriptEvaluate scriptEval(cx); + if (!scriptEval.StartEvaluating(aJSObj)) + return NS_ERROR_FAILURE; + + Rooted idArray(cx, IdVector(cx)); + if (!JS_Enumerate(cx, aJSObj, &idArray)) + return NS_ERROR_FAILURE; + + nsCOMArray propertyArray(idArray.length()); + RootedId idName(cx); + for (size_t i = 0; i < idArray.length(); i++) { + idName = idArray[i]; + + nsCOMPtr value; + nsresult rv; + if (!GetNamedPropertyAsVariantRaw(ccx, aJSObj, idName, + getter_AddRefs(value), &rv)) { + if (NS_FAILED(rv)) + return rv; + return NS_ERROR_FAILURE; + } + + RootedValue jsvalName(cx); + if (!JS_IdToValue(cx, idName, &jsvalName)) + return NS_ERROR_FAILURE; + + JSString* name = ToString(cx, jsvalName); + if (!name) + return NS_ERROR_FAILURE; + + nsAutoJSString autoStr; + if (!autoStr.init(cx, name)) + return NS_ERROR_FAILURE; + + nsCOMPtr property = + new xpcProperty(autoStr.get(), (uint32_t)autoStr.Length(), value); + + if (!propertyArray.AppendObject(property)) + return NS_ERROR_FAILURE; + } + + return NS_NewArrayEnumerator(aEnumerate, propertyArray); +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(xpcProperty, nsIProperty) + +xpcProperty::xpcProperty(const char16_t* aName, uint32_t aNameLen, + nsIVariant* aValue) + : mName(aName, aNameLen), mValue(aValue) +{ +} + +NS_IMETHODIMP xpcProperty::GetName(nsAString & aName) +{ + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP xpcProperty::GetValue(nsIVariant * *aValue) +{ + nsCOMPtr rval = mValue; + rval.forget(aValue); + return NS_OK; +} + +/***************************************************************************/ +// This 'WrappedJSIdentity' class and singleton allow us to figure out if +// any given nsISupports* is implemented by a WrappedJS object. This is done +// using a QueryInterface call on the interface pointer with our ID. If +// that call returns NS_OK and the pointer is to our singleton, then the +// interface must be implemented by a WrappedJS object. NOTE: the +// 'WrappedJSIdentity' object is not a real XPCOM object and should not be +// used for anything else (hence it is declared in this implementation file). + +// {5C5C3BB0-A9BA-11d2-BA64-00805F8A5DD7} +#define NS_IXPCONNECT_WRAPPED_JS_IDENTITY_CLASS_IID \ +{ 0x5c5c3bb0, 0xa9ba, 0x11d2, \ + { 0xba, 0x64, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 } } + +class WrappedJSIdentity +{ + // no instance methods... +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECT_WRAPPED_JS_IDENTITY_CLASS_IID) + + static void* GetSingleton() + { + static WrappedJSIdentity* singleton = nullptr; + if (!singleton) + singleton = new WrappedJSIdentity(); + return (void*) singleton; + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(WrappedJSIdentity, + NS_IXPCONNECT_WRAPPED_JS_IDENTITY_CLASS_IID) + +/***************************************************************************/ + +// static +bool +nsXPCWrappedJSClass::IsWrappedJS(nsISupports* aPtr) +{ + void* result; + NS_PRECONDITION(aPtr, "null pointer"); + return aPtr && + NS_OK == aPtr->QueryInterface(NS_GET_IID(WrappedJSIdentity), &result) && + result == WrappedJSIdentity::GetSingleton(); +} + +NS_IMETHODIMP +nsXPCWrappedJSClass::DelegatedQueryInterface(nsXPCWrappedJS* self, + REFNSIID aIID, + void** aInstancePtr) +{ + if (aIID.Equals(NS_GET_IID(nsIXPConnectJSObjectHolder))) { + NS_ADDREF(self); + *aInstancePtr = (void*) static_cast(self); + return NS_OK; + } + + // Objects internal to xpconnect are the only objects that even know *how* + // to ask for this iid. And none of them bother refcounting the thing. + if (aIID.Equals(NS_GET_IID(WrappedJSIdentity))) { + // asking to find out if this is a wrapper object + *aInstancePtr = WrappedJSIdentity::GetSingleton(); + return NS_OK; + } + + if (aIID.Equals(NS_GET_IID(nsIPropertyBag))) { + // We only want to expose one implementation from our aggregate. + nsXPCWrappedJS* root = self->GetRootWrapper(); + + if (!root->IsValid()) { + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + + NS_ADDREF(root); + *aInstancePtr = (void*) static_cast(root); + return NS_OK; + } + + // We can't have a cached wrapper. + if (aIID.Equals(NS_GET_IID(nsWrapperCache))) { + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + + // QI on an XPCWrappedJS can run script, so we need an AutoEntryScript. + // This is inherently Gecko-specific. + // We check both nativeGlobal and nativeGlobal->GetGlobalJSObject() even + // though we have derived nativeGlobal from the JS global, because we know + // there are cases where this can happen. See bug 1094953. + nsIGlobalObject* nativeGlobal = + NativeGlobal(js::GetGlobalForObjectCrossCompartment(self->GetJSObject())); + NS_ENSURE_TRUE(nativeGlobal, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(nativeGlobal->GetGlobalJSObject(), NS_ERROR_FAILURE); + AutoEntryScript aes(nativeGlobal, "XPCWrappedJS QueryInterface", + /* aIsMainThread = */ true); + XPCCallContext ccx(aes.cx()); + if (!ccx.IsValid()) { + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + + // We support nsISupportsWeakReference iff the root wrapped JSObject + // claims to support it in its QueryInterface implementation. + if (aIID.Equals(NS_GET_IID(nsISupportsWeakReference))) { + // We only want to expose one implementation from our aggregate. + nsXPCWrappedJS* root = self->GetRootWrapper(); + + // Fail if JSObject doesn't claim support for nsISupportsWeakReference + if (!root->IsValid() || + !CallQueryInterfaceOnJSObject(ccx, root->GetJSObject(), aIID)) { + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } + + NS_ADDREF(root); + *aInstancePtr = (void*) static_cast(root); + return NS_OK; + } + + // Checks for any existing wrapper explicitly constructed for this iid. + // This includes the current 'self' wrapper. This also deals with the + // nsISupports case (for which it returns mRoot). + // Also check if asking for an interface from which one of our wrappers inherits. + if (nsXPCWrappedJS* sibling = self->FindOrFindInherited(aIID)) { + NS_ADDREF(sibling); + *aInstancePtr = sibling->GetXPTCStub(); + return NS_OK; + } + + // Check if the desired interface is a function interface. If so, we don't + // want to QI, because the function almost certainly doesn't have a QueryInterface + // property, and doesn't need one. + bool isFunc = false; + nsCOMPtr info; + nsXPConnect::XPConnect()->GetInfoForIID(&aIID, getter_AddRefs(info)); + if (info && NS_SUCCEEDED(info->IsFunction(&isFunc)) && isFunc) { + RefPtr wrapper; + RootedObject obj(RootingCx(), self->GetJSObject()); + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(obj, aIID, getter_AddRefs(wrapper)); + + // Do the same thing we do for the "check for any existing wrapper" case above. + if (NS_SUCCEEDED(rv) && wrapper) { + *aInstancePtr = wrapper.forget().take()->GetXPTCStub(); + } + return rv; + } + + // else we do the more expensive stuff... + + // check if the JSObject claims to implement this interface + RootedObject jsobj(ccx, CallQueryInterfaceOnJSObject(ccx, self->GetJSObject(), + aIID)); + if (jsobj) { + // We can't use XPConvert::JSObject2NativeInterface() here + // since that can find a XPCWrappedNative directly on the + // proto chain, and we don't want that here. We need to find + // the actual JS object that claimed it supports the interface + // we're looking for or we'll potentially bypass security + // checks etc by calling directly through to a native found on + // the prototype chain. + // + // Instead, simply do the nsXPCWrappedJS part of + // XPConvert::JSObject2NativeInterface() here to make sure we + // get a new (or used) nsXPCWrappedJS. + RefPtr wrapper; + nsresult rv = nsXPCWrappedJS::GetNewOrUsed(jsobj, aIID, getter_AddRefs(wrapper)); + if (NS_SUCCEEDED(rv) && wrapper) { + // We need to go through the QueryInterface logic to make + // this return the right thing for the various 'special' + // interfaces; e.g. nsIPropertyBag. + rv = wrapper->QueryInterface(aIID, aInstancePtr); + return rv; + } + } + + // else... + // no can do + *aInstancePtr = nullptr; + return NS_NOINTERFACE; +} + +JSObject* +nsXPCWrappedJSClass::GetRootJSObject(JSContext* cx, JSObject* aJSObjArg) +{ + RootedObject aJSObj(cx, aJSObjArg); + JSObject* result = CallQueryInterfaceOnJSObject(cx, aJSObj, + NS_GET_IID(nsISupports)); + if (!result) + result = aJSObj; + JSObject* inner = js::UncheckedUnwrap(result); + if (inner) + return inner; + return result; +} + +bool +nsXPCWrappedJSClass::GetArraySizeFromParam(JSContext* cx, + const XPTMethodDescriptor* method, + const nsXPTParamInfo& param, + uint16_t methodIndex, + uint8_t paramIndex, + nsXPTCMiniVariant* nativeParams, + uint32_t* result) const +{ + uint8_t argnum; + nsresult rv; + + rv = mInfo->GetSizeIsArgNumberForParam(methodIndex, ¶m, 0, &argnum); + if (NS_FAILED(rv)) + return false; + + const nsXPTParamInfo& arg_param = method->params[argnum]; + + // This should be enforced by the xpidl compiler, but it's not. + // See bug 695235. + MOZ_ASSERT(arg_param.GetType().TagPart() == nsXPTType::T_U32, + "size_is references parameter of invalid type."); + + if (arg_param.IsIndirect()) + *result = *(uint32_t*)nativeParams[argnum].val.p; + else + *result = nativeParams[argnum].val.u32; + + return true; +} + +bool +nsXPCWrappedJSClass::GetInterfaceTypeFromParam(JSContext* cx, + const XPTMethodDescriptor* method, + const nsXPTParamInfo& param, + uint16_t methodIndex, + const nsXPTType& type, + nsXPTCMiniVariant* nativeParams, + nsID* result) const +{ + uint8_t type_tag = type.TagPart(); + + if (type_tag == nsXPTType::T_INTERFACE) { + if (NS_SUCCEEDED(GetInterfaceInfo()-> + GetIIDForParamNoAlloc(methodIndex, ¶m, result))) { + return true; + } + } else if (type_tag == nsXPTType::T_INTERFACE_IS) { + uint8_t argnum; + nsresult rv; + rv = mInfo->GetInterfaceIsArgNumberForParam(methodIndex, + ¶m, &argnum); + if (NS_FAILED(rv)) + return false; + + const nsXPTParamInfo& arg_param = method->params[argnum]; + const nsXPTType& arg_type = arg_param.GetType(); + + if (arg_type.TagPart() == nsXPTType::T_IID) { + if (arg_param.IsIndirect()) { + nsID** p = (nsID**) nativeParams[argnum].val.p; + if (!p || !*p) + return false; + *result = **p; + } else { + nsID* p = (nsID*) nativeParams[argnum].val.p; + if (!p) + return false; + *result = *p; + } + return true; + } + } + return false; +} + +/* static */ void +nsXPCWrappedJSClass::CleanupPointerArray(const nsXPTType& datum_type, + uint32_t array_count, + void** arrayp) +{ + if (datum_type.IsInterfacePointer()) { + nsISupports** pp = (nsISupports**) arrayp; + for (uint32_t k = 0; k < array_count; k++) { + nsISupports* p = pp[k]; + NS_IF_RELEASE(p); + } + } else { + void** pp = (void**) arrayp; + for (uint32_t k = 0; k < array_count; k++) { + void* p = pp[k]; + if (p) free(p); + } + } +} + +/* static */ void +nsXPCWrappedJSClass::CleanupPointerTypeObject(const nsXPTType& type, + void** pp) +{ + MOZ_ASSERT(pp,"null pointer"); + if (type.IsInterfacePointer()) { + nsISupports* p = *((nsISupports**)pp); + if (p) p->Release(); + } else { + void* p = *((void**)pp); + if (p) free(p); + } +} + +void +nsXPCWrappedJSClass::CleanupOutparams(JSContext* cx, uint16_t methodIndex, + const nsXPTMethodInfo* info, nsXPTCMiniVariant* nativeParams, + bool inOutOnly, uint8_t n) const +{ + // clean up any 'out' params handed in + for (uint8_t i = 0; i < n; i++) { + const nsXPTParamInfo& param = info->params[i]; + if (!param.IsOut()) + continue; + + const nsXPTType& type = param.GetType(); + if (!type.deprecated_IsPointer()) + continue; + void* p = nativeParams[i].val.p; + if (!p) + continue; + + // The inOutOnly flag was introduced when consolidating two very + // similar code paths in CallMethod in bug 1175513. I don't know + // if and why the difference is necessary. + if (!inOutOnly || param.IsIn()) { + if (type.IsArray()) { + void** pp = *static_cast(p); + if (pp) { + // we need to get the array length and iterate the items + uint32_t array_count; + nsXPTType datum_type; + + if (NS_SUCCEEDED(mInfo->GetTypeForParam(methodIndex, ¶m, + 1, &datum_type)) && + datum_type.deprecated_IsPointer() && + GetArraySizeFromParam(cx, info, param, methodIndex, + i, nativeParams, &array_count) && + array_count) { + + CleanupPointerArray(datum_type, array_count, pp); + } + + // always release the array if it is inout + free(pp); + } + } else { + CleanupPointerTypeObject(type, static_cast(p)); + } + } + *static_cast(p) = nullptr; + } +} + +nsresult +nsXPCWrappedJSClass::CheckForException(XPCCallContext & ccx, + AutoEntryScript& aes, + const char * aPropertyName, + const char * anInterfaceName, + nsIException* aSyntheticException) +{ + JSContext * cx = ccx.GetJSContext(); + MOZ_ASSERT(cx == aes.cx()); + nsCOMPtr xpc_exception = aSyntheticException; + /* this one would be set by our error reporter */ + + XPCJSContext* xpccx = XPCJSContext::Get(); + + // Get this right away in case we do something below to cause JS code + // to run. + nsresult pending_result = xpccx->GetPendingResult(); + + RootedValue js_exception(cx); + bool is_js_exception = JS_GetPendingException(cx, &js_exception); + + /* JS might throw an expection whether the reporter was called or not */ + if (is_js_exception) { + if (!xpc_exception) + XPCConvert::JSValToXPCException(&js_exception, anInterfaceName, + aPropertyName, + getter_AddRefs(xpc_exception)); + + /* cleanup and set failed even if we can't build an exception */ + if (!xpc_exception) { + xpccx->SetPendingException(nullptr); // XXX necessary? + } + } + + // Clear the pending exception now, because xpc_exception might be JS- + // implemented, so invoking methods on it might re-enter JS, which we can't + // do with an exception on the stack. + aes.ClearException(); + + if (xpc_exception) { + nsresult e_result; + if (NS_SUCCEEDED(xpc_exception->GetResult(&e_result))) { + // Figure out whether or not we should report this exception. + bool reportable = xpc_IsReportableErrorCode(e_result); + if (reportable) { + // Ugly special case for GetInterface. It's "special" in the + // same way as QueryInterface in that a failure is not + // exceptional and shouldn't be reported. We have to do this + // check here instead of in xpcwrappedjs (like we do for QI) to + // avoid adding extra code to all xpcwrappedjs objects. + if (e_result == NS_ERROR_NO_INTERFACE && + !strcmp(anInterfaceName, "nsIInterfaceRequestor") && + !strcmp(aPropertyName, "getInterface")) { + reportable = false; + } + + // More special case, see bug 877760. + if (e_result == NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED) { + reportable = false; + } + } + + // Try to use the error reporter set on the context to handle this + // error if it came from a JS exception. + if (reportable && is_js_exception) + { + // Note that we cleared the exception above, so we need to set it again, + // just so that we can tell the JS engine to pass it back to us via the + // error reporting callback. This is all very dumb. + JS_SetPendingException(cx, js_exception); + aes.ReportException(); + reportable = false; + } + + if (reportable) { + if (nsContentUtils::DOMWindowDumpEnabled()) { + static const char line[] = + "************************************************************\n"; + static const char preamble[] = + "* Call to xpconnect wrapped JSObject produced this error: *\n"; + static const char cant_get_text[] = + "FAILED TO GET TEXT FROM EXCEPTION\n"; + + fputs(line, stdout); + fputs(preamble, stdout); + nsCString text; + if (NS_SUCCEEDED(xpc_exception->ToString(cx, text)) && + !text.IsEmpty()) { + fputs(text.get(), stdout); + fputs("\n", stdout); + } else + fputs(cant_get_text, stdout); + fputs(line, stdout); + } + + // Log the exception to the JS Console, so that users can do + // something with it. + nsCOMPtr consoleService + (do_GetService(XPC_CONSOLE_CONTRACTID)); + if (nullptr != consoleService) { + nsresult rv; + nsCOMPtr scriptError; + nsCOMPtr errorData; + rv = xpc_exception->GetData(getter_AddRefs(errorData)); + if (NS_SUCCEEDED(rv)) + scriptError = do_QueryInterface(errorData); + + if (nullptr == scriptError) { + // No luck getting one from the exception, so + // try to cook one up. + scriptError = do_CreateInstance(XPC_SCRIPT_ERROR_CONTRACTID); + if (nullptr != scriptError) { + nsCString newMessage; + rv = xpc_exception->ToString(cx, newMessage); + if (NS_SUCCEEDED(rv)) { + // try to get filename, lineno from the first + // stack frame location. + int32_t lineNumber = 0; + nsString sourceName; + + nsCOMPtr location; + xpc_exception-> + GetLocation(getter_AddRefs(location)); + if (location) { + // Get line number w/o checking; 0 is ok. + location->GetLineNumber(cx, &lineNumber); + + // get a filename. + rv = location->GetFilename(cx, sourceName); + } + + rv = scriptError->InitWithWindowID(NS_ConvertUTF8toUTF16(newMessage), + sourceName, + EmptyString(), + lineNumber, 0, 0, + "XPConnect JavaScript", + nsJSUtils::GetCurrentlyRunningCodeInnerWindowID(cx)); + if (NS_FAILED(rv)) + scriptError = nullptr; + } + } + } + if (nullptr != scriptError) + consoleService->LogMessage(scriptError); + } + } + // Whether or not it passes the 'reportable' test, it might + // still be an error and we have to do the right thing here... + if (NS_FAILED(e_result)) { + xpccx->SetPendingException(xpc_exception); + return e_result; + } + } + } else { + // see if JS code signaled failure result without throwing exception + if (NS_FAILED(pending_result)) { + return pending_result; + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsXPCWrappedJSClass::CallMethod(nsXPCWrappedJS* wrapper, uint16_t methodIndex, + const XPTMethodDescriptor* info_, + nsXPTCMiniVariant* nativeParams) +{ + Value* sp = nullptr; + Value* argv = nullptr; + uint8_t i; + nsresult retval = NS_ERROR_FAILURE; + bool success; + bool readyToDoTheCall = false; + nsID param_iid; + const nsXPTMethodInfo* info = static_cast(info_); + const char* name = info->name; + bool foundDependentParam; + + // Make sure not to set the callee on ccx until after we've gone through + // the whole nsIXPCFunctionThisTranslator bit. That code uses ccx to + // convert natives to JSObjects, but we do NOT plan to pass those JSObjects + // to our real callee. + // + // We're about to call into script via an XPCWrappedJS, so we need an + // AutoEntryScript. This is probably Gecko-specific at this point, and + // definitely will be when we turn off XPConnect for the web. + nsIGlobalObject* nativeGlobal = + NativeGlobal(js::GetGlobalForObjectCrossCompartment(wrapper->GetJSObject())); + AutoEntryScript aes(nativeGlobal, "XPCWrappedJS method call", + /* aIsMainThread = */ true); + XPCCallContext ccx(aes.cx()); + if (!ccx.IsValid()) + return retval; + + JSContext* cx = ccx.GetJSContext(); + + if (!cx || !IsReflectable(methodIndex)) + return NS_ERROR_FAILURE; + + // [implicit_jscontext] and [optional_argc] have a different calling + // convention, which we don't support for JS-implemented components. + if (info->WantsOptArgc() || info->WantsContext()) { + const char* str = "IDL methods marked with [implicit_jscontext] " + "or [optional_argc] may not be implemented in JS"; + // Throw and warn for good measure. + JS_ReportErrorASCII(cx, "%s", str); + NS_WARNING(str); + return CheckForException(ccx, aes, name, GetInterfaceName()); + } + + RootedValue fval(cx); + RootedObject obj(cx, wrapper->GetJSObject()); + RootedObject thisObj(cx, obj); + + JSAutoCompartment ac(cx, obj); + + AutoValueVector args(cx); + AutoScriptEvaluate scriptEval(cx); + + XPCJSContext* xpccx = XPCJSContext::Get(); + AutoSavePendingResult apr(xpccx); + + // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this. + uint8_t paramCount = info->num_args; + uint8_t argc = paramCount - + (paramCount && XPT_PD_IS_RETVAL(info->params[paramCount-1].flags) ? 1 : 0); + + if (!scriptEval.StartEvaluating(obj)) + goto pre_call_clean_up; + + xpccx->SetPendingException(nullptr); + + // We use js_Invoke so that the gcthings we use as args will be rooted by + // the engine as we do conversions and prepare to do the function call. + + // setup stack + + // if this isn't a function call then we don't need to push extra stuff + if (!(XPT_MD_IS_SETTER(info->flags) || XPT_MD_IS_GETTER(info->flags))) { + // We get fval before allocating the stack to avoid gc badness that can + // happen if the GetProperty call leaves our request and the gc runs + // while the stack we allocate contains garbage. + + // If the interface is marked as a [function] then we will assume that + // our JSObject is a function and not an object with a named method. + + bool isFunction; + if (NS_FAILED(mInfo->IsFunction(&isFunction))) + goto pre_call_clean_up; + + // In the xpidl [function] case we are making sure now that the + // JSObject is callable. If it is *not* callable then we silently + // fallback to looking up the named property... + // (because jst says he thinks this fallback is 'The Right Thing'.) + // + // In the normal (non-function) case we just lookup the property by + // name and as long as the object has such a named property we go ahead + // and try to make the call. If it turns out the named property is not + // a callable object then the JS engine will throw an error and we'll + // pass this along to the caller as an exception/result code. + + fval = ObjectValue(*obj); + if (isFunction && + JS_TypeOfValue(ccx, fval) == JSTYPE_FUNCTION) { + + // We may need to translate the 'this' for the function object. + + if (paramCount) { + const nsXPTParamInfo& firstParam = info->params[0]; + if (firstParam.IsIn()) { + const nsXPTType& firstType = firstParam.GetType(); + + if (firstType.IsInterfacePointer()) { + nsIXPCFunctionThisTranslator* translator; + + IID2ThisTranslatorMap* map = + mContext->GetThisTranslatorMap(); + + translator = map->Find(mIID); + + if (translator) { + nsCOMPtr newThis; + if (NS_FAILED(translator-> + TranslateThis((nsISupports*)nativeParams[0].val.p, + getter_AddRefs(newThis)))) { + goto pre_call_clean_up; + } + if (newThis) { + RootedValue v(cx); + xpcObjectHelper helper(newThis); + bool ok = + XPCConvert::NativeInterface2JSObject( + &v, nullptr, helper, nullptr, + false, nullptr); + if (!ok) { + goto pre_call_clean_up; + } + thisObj = v.toObjectOrNull(); + if (!JS_WrapObject(cx, &thisObj)) + goto pre_call_clean_up; + } + } + } + } + } + } else { + if (!JS_GetProperty(cx, obj, name, &fval)) + goto pre_call_clean_up; + // XXX We really want to factor out the error reporting better and + // specifically report the failure to find a function with this name. + // This is what we do below if the property is found but is not a + // function. We just need to factor better so we can get to that + // reporting path from here. + + thisObj = obj; + } + } + + if (!args.resize(argc)) { + retval = NS_ERROR_OUT_OF_MEMORY; + goto pre_call_clean_up; + } + + argv = args.begin(); + sp = argv; + + // build the args + // NB: This assignment *looks* wrong because we haven't yet called our + // function. However, we *have* already entered the compartmen that we're + // about to call, and that's the global that we want here. In other words: + // we're trusting the JS engine to come up with a good global to use for + // our object (whatever it was). + for (i = 0; i < argc; i++) { + const nsXPTParamInfo& param = info->params[i]; + const nsXPTType& type = param.GetType(); + nsXPTType datum_type; + uint32_t array_count; + bool isArray = type.IsArray(); + RootedValue val(cx, NullValue()); + bool isSizedString = isArray ? + false : + type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || + type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; + + + // verify that null was not passed for 'out' param + if (param.IsOut() && !nativeParams[i].val.p) { + retval = NS_ERROR_INVALID_ARG; + goto pre_call_clean_up; + } + + if (isArray) { + if (NS_FAILED(mInfo->GetTypeForParam(methodIndex, ¶m, 1, + &datum_type))) + goto pre_call_clean_up; + } else + datum_type = type; + + if (param.IsIn()) { + nsXPTCMiniVariant* pv; + + if (param.IsIndirect()) + pv = (nsXPTCMiniVariant*) nativeParams[i].val.p; + else + pv = &nativeParams[i]; + + if (datum_type.IsInterfacePointer() && + !GetInterfaceTypeFromParam(cx, info, param, methodIndex, + datum_type, nativeParams, + ¶m_iid)) + goto pre_call_clean_up; + + if (isArray || isSizedString) { + if (!GetArraySizeFromParam(cx, info, param, methodIndex, + i, nativeParams, &array_count)) + goto pre_call_clean_up; + } + + if (isArray) { + if (!XPCConvert::NativeArray2JS(&val, + (const void**)&pv->val, + datum_type, ¶m_iid, + array_count, nullptr)) + goto pre_call_clean_up; + } else if (isSizedString) { + if (!XPCConvert::NativeStringWithSize2JS(&val, + (const void*)&pv->val, + datum_type, + array_count, nullptr)) + goto pre_call_clean_up; + } else { + if (!XPCConvert::NativeData2JS(&val, &pv->val, type, + ¶m_iid, nullptr)) + goto pre_call_clean_up; + } + } + + if (param.IsOut() || param.IsDipper()) { + // create an 'out' object + RootedObject out_obj(cx, NewOutObject(cx)); + if (!out_obj) { + retval = NS_ERROR_OUT_OF_MEMORY; + goto pre_call_clean_up; + } + + if (param.IsIn()) { + if (!JS_SetPropertyById(cx, out_obj, + mContext->GetStringID(XPCJSContext::IDX_VALUE), + val)) { + goto pre_call_clean_up; + } + } + *sp++ = JS::ObjectValue(*out_obj); + } else + *sp++ = val; + } + + readyToDoTheCall = true; + +pre_call_clean_up: + // clean up any 'out' params handed in + CleanupOutparams(cx, methodIndex, info, nativeParams, /* inOutOnly = */ true, paramCount); + + // Make sure "this" doesn't get deleted during this call. + nsCOMPtr kungFuDeathGrip(this); + + if (!readyToDoTheCall) + return retval; + + // do the deed - note exceptions + + MOZ_ASSERT(!aes.HasException()); + + nsCOMPtr syntheticException; + RootedValue rval(cx); + if (XPT_MD_IS_GETTER(info->flags)) { + success = JS_GetProperty(cx, obj, name, &rval); + } else if (XPT_MD_IS_SETTER(info->flags)) { + rval = *argv; + success = JS_SetProperty(cx, obj, name, rval); + } else { + if (!fval.isPrimitive()) { + success = JS_CallFunctionValue(cx, thisObj, fval, args, &rval); + } else { + // The property was not an object so can't be a function. + // Let's build and 'throw' an exception. + + static const nsresult code = + NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED; + static const char format[] = "%s \"%s\""; + const char * msg; + char* sz = nullptr; + + if (nsXPCException::NameAndFormatForNSResult(code, nullptr, &msg) && msg) + sz = JS_smprintf(format, msg, name); + + XPCConvert::ConstructException(code, sz, GetInterfaceName(), name, + nullptr, getter_AddRefs(syntheticException), + nullptr, nullptr); + if (sz) + JS_smprintf_free(sz); + success = false; + } + } + + if (!success) + return CheckForException(ccx, aes, name, GetInterfaceName(), + syntheticException); + + XPCJSContext::Get()->SetPendingException(nullptr); // XXX necessary? + + // convert out args and result + // NOTE: this is the total number of native params, not just the args + // Convert independent params only. + // When we later convert the dependent params (if any) we will know that + // the params upon which they depend will have already been converted - + // regardless of ordering. + + foundDependentParam = false; + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->params[i]; + MOZ_ASSERT(!param.IsShared(), "[shared] implies [noscript]!"); + if (!param.IsOut() && !param.IsDipper()) + continue; + + const nsXPTType& type = param.GetType(); + if (type.IsDependent()) { + foundDependentParam = true; + continue; + } + + RootedValue val(cx); + uint8_t type_tag = type.TagPart(); + nsXPTCMiniVariant* pv; + + if (param.IsDipper()) + pv = (nsXPTCMiniVariant*) &nativeParams[i].val.p; + else + pv = (nsXPTCMiniVariant*) nativeParams[i].val.p; + + if (param.IsRetval()) + val = rval; + else if (argv[i].isPrimitive()) + break; + else { + RootedObject obj(cx, &argv[i].toObject()); + if (!JS_GetPropertyById(cx, obj, + mContext->GetStringID(XPCJSContext::IDX_VALUE), + &val)) + break; + } + + // setup allocator and/or iid + + if (type_tag == nsXPTType::T_INTERFACE) { + if (NS_FAILED(GetInterfaceInfo()-> + GetIIDForParamNoAlloc(methodIndex, ¶m, + ¶m_iid))) + break; + } + + if (!XPCConvert::JSData2Native(&pv->val, val, type, + ¶m_iid, nullptr)) + break; + } + + // if any params were dependent, then we must iterate again to convert them. + if (foundDependentParam && i == paramCount) { + for (i = 0; i < paramCount; i++) { + const nsXPTParamInfo& param = info->params[i]; + if (!param.IsOut()) + continue; + + const nsXPTType& type = param.GetType(); + if (!type.IsDependent()) + continue; + + RootedValue val(cx); + nsXPTCMiniVariant* pv; + nsXPTType datum_type; + uint32_t array_count; + bool isArray = type.IsArray(); + bool isSizedString = isArray ? + false : + type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || + type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; + + pv = (nsXPTCMiniVariant*) nativeParams[i].val.p; + + if (param.IsRetval()) + val = rval; + else { + RootedObject obj(cx, &argv[i].toObject()); + if (!JS_GetPropertyById(cx, obj, + mContext->GetStringID(XPCJSContext::IDX_VALUE), + &val)) + break; + } + + // setup allocator and/or iid + + if (isArray) { + if (NS_FAILED(mInfo->GetTypeForParam(methodIndex, ¶m, 1, + &datum_type))) + break; + } else + datum_type = type; + + if (datum_type.IsInterfacePointer()) { + if (!GetInterfaceTypeFromParam(cx, info, param, methodIndex, + datum_type, nativeParams, + ¶m_iid)) + break; + } + + if (isArray || isSizedString) { + if (!GetArraySizeFromParam(cx, info, param, methodIndex, + i, nativeParams, &array_count)) + break; + } + + if (isArray) { + if (array_count && + !XPCConvert::JSArray2Native((void**)&pv->val, val, + array_count, datum_type, + ¶m_iid, nullptr)) + break; + } else if (isSizedString) { + if (!XPCConvert::JSStringWithSize2Native((void*)&pv->val, val, + array_count, datum_type, + nullptr)) + break; + } else { + if (!XPCConvert::JSData2Native(&pv->val, val, type, + ¶m_iid, + nullptr)) + break; + } + } + } + + if (i != paramCount) { + // We didn't manage all the result conversions! + // We have to cleanup any junk that *did* get converted. + CleanupOutparams(cx, methodIndex, info, nativeParams, /* inOutOnly = */ false, i); + } else { + // set to whatever the JS code might have set as the result + retval = xpccx->GetPendingResult(); + } + + return retval; +} + +const char* +nsXPCWrappedJSClass::GetInterfaceName() +{ + if (!mName) + mInfo->GetName(&mName); + return mName; +} + +static const JSClass XPCOutParamClass = { + "XPCOutParam", + 0, + JS_NULL_CLASS_OPS +}; + +bool +xpc::IsOutObject(JSContext* cx, JSObject* obj) +{ + return js::GetObjectJSClass(obj) == &XPCOutParamClass; +} + +JSObject* +xpc::NewOutObject(JSContext* cx) +{ + return JS_NewObject(cx, &XPCOutParamClass); +} + + +NS_IMETHODIMP +nsXPCWrappedJSClass::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth-- ; + XPC_LOG_ALWAYS(("nsXPCWrappedJSClass @ %x with mRefCnt = %d", this, mRefCnt.get())); + XPC_LOG_INDENT(); + char* name; + mInfo->GetName(&name); + XPC_LOG_ALWAYS(("interface name is %s", name)); + if (name) + free(name); + char * iid = mIID.ToString(); + XPC_LOG_ALWAYS(("IID number is %s", iid ? iid : "invalid")); + if (iid) + free(iid); + XPC_LOG_ALWAYS(("InterfaceInfo @ %x", mInfo.get())); + uint16_t methodCount = 0; + if (depth) { + uint16_t i; + nsCOMPtr parent; + XPC_LOG_INDENT(); + mInfo->GetParent(getter_AddRefs(parent)); + XPC_LOG_ALWAYS(("parent @ %x", parent.get())); + mInfo->GetMethodCount(&methodCount); + XPC_LOG_ALWAYS(("MethodCount = %d", methodCount)); + mInfo->GetConstantCount(&i); + XPC_LOG_ALWAYS(("ConstantCount = %d", i)); + XPC_LOG_OUTDENT(); + } + XPC_LOG_ALWAYS(("mContext @ %x", mContext)); + XPC_LOG_ALWAYS(("mDescriptors @ %x count = %d", mDescriptors, methodCount)); + if (depth && mDescriptors && methodCount) { + depth--; + XPC_LOG_INDENT(); + for (uint16_t i = 0; i < methodCount; i++) { + XPC_LOG_ALWAYS(("Method %d is %s%s", \ + i, IsReflectable(i) ? "":" NOT ","reflectable")); + } + XPC_LOG_OUTDENT(); + depth++; + } + XPC_LOG_OUTDENT(); +#endif + return NS_OK; +} diff --git a/js/xpconnect/src/XPCWrappedNative.cpp b/js/xpconnect/src/XPCWrappedNative.cpp new file mode 100644 index 000000000..acf92f3c3 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNative.cpp @@ -0,0 +1,2325 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Wrapper object for reflecting native xpcom objects into JavaScript. */ + +#include "xpcprivate.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "nsWrapperCacheInlines.h" +#include "XPCLog.h" +#include "jsprf.h" +#include "jsfriendapi.h" +#include "AccessCheck.h" +#include "WrapperFactory.h" +#include "XrayWrapper.h" + +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" + +#include +#include "mozilla/DeferredFinalize.h" +#include "mozilla/Likely.h" +#include "mozilla/Unused.h" +#include "mozilla/Sprintf.h" +#include "mozilla/dom/BindingUtils.h" +#include + +using namespace xpc; +using namespace mozilla; +using namespace mozilla::dom; +using namespace JS; + +/***************************************************************************/ + +NS_IMPL_CYCLE_COLLECTION_CLASS(XPCWrappedNative) + +// No need to unlink the JS objects: if the XPCWrappedNative is cycle +// collected then its mFlatJSObject will be cycle collected too and +// finalization of the mFlatJSObject will unlink the JS objects (see +// XPC_WN_NoHelper_Finalize and FlatJSObjectFinalized). +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPCWrappedNative) + tmp->ExpireWrapper(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(XPCWrappedNative) + if (!tmp->IsValid()) + return NS_OK; + + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[72]; + XPCNativeScriptableInfo* si = tmp->GetScriptableInfo(); + if (si) + SprintfLiteral(name, "XPCWrappedNative (%s)", si->GetJSClass()->name); + else + SprintfLiteral(name, "XPCWrappedNative"); + + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(XPCWrappedNative, tmp->mRefCnt.get()) + } + + if (tmp->HasExternalReference()) { + + // If our refcount is > 1, our reference to the flat JS object is + // considered "strong", and we're going to traverse it. + // + // If our refcount is <= 1, our reference to the flat JS object is + // considered "weak", and we're *not* going to traverse it. + // + // This reasoning is in line with the slightly confusing lifecycle rules + // for XPCWrappedNatives, described in a larger comment below and also + // on our wiki at http://wiki.mozilla.org/XPConnect_object_wrapping + + JSObject* obj = tmp->GetFlatJSObjectPreserveColor(); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFlatJSObject"); + cb.NoteJSChild(JS::GCCellPtr(obj)); + } + + // XPCWrappedNative keeps its native object alive. + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mIdentity"); + cb.NoteXPCOMChild(tmp->GetIdentityObject()); + + tmp->NoteTearoffs(cb); + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +void +XPCWrappedNative::Suspect(nsCycleCollectionNoteRootCallback& cb) +{ + if (!IsValid() || IsWrapperExpired()) + return; + + MOZ_ASSERT(NS_IsMainThread(), + "Suspecting wrapped natives from non-main thread"); + + // Only record objects that might be part of a cycle as roots, unless + // the callback wants all traces (a debug feature). Do this even if + // the XPCWN doesn't own the JS reflector object in case the reflector + // keeps alive other C++ things. This is safe because if the reflector + // had died the reference from the XPCWN to it would have been cleared. + JSObject* obj = GetFlatJSObjectPreserveColor(); + if (JS::ObjectIsMarkedGray(obj) || cb.WantAllTraces()) + cb.NoteJSRoot(obj); +} + +void +XPCWrappedNative::NoteTearoffs(nsCycleCollectionTraversalCallback& cb) +{ + // Tearoffs hold their native object alive. If their JS object hasn't been + // finalized yet we'll note the edge between the JS object and the native + // (see nsXPConnect::Traverse), but if their JS object has been finalized + // then the tearoff is only reachable through the XPCWrappedNative, so we + // record an edge here. + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) { + JSObject* jso = to->GetJSObjectPreserveColor(); + if (!jso) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "tearoff's mNative"); + cb.NoteXPCOMChild(to->GetNative()); + } + } +} + +#ifdef XPC_CHECK_CLASSINFO_CLAIMS +static void DEBUG_CheckClassInfoClaims(XPCWrappedNative* wrapper); +#else +#define DEBUG_CheckClassInfoClaims(wrapper) ((void)0) +#endif + +/***************************************************************************/ +static nsresult +FinishCreate(XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + nsWrapperCache* cache, + XPCWrappedNative* inWrapper, + XPCWrappedNative** resultWrapper); + +// static +// +// This method handles the special case of wrapping a new global object. +// +// The normal code path for wrapping natives goes through +// XPCConvert::NativeInterface2JSObject, XPCWrappedNative::GetNewOrUsed, +// and finally into XPCWrappedNative::Init. Unfortunately, this path assumes +// very early on that we have an XPCWrappedNativeScope and corresponding global +// JS object, which are the very things we need to create here. So we special- +// case the logic and do some things in a different order. +nsresult +XPCWrappedNative::WrapNewGlobal(xpcObjectHelper& nativeHelper, + nsIPrincipal* principal, + bool initStandardClasses, + JS::CompartmentOptions& aOptions, + XPCWrappedNative** wrappedGlobal) +{ + AutoJSContext cx; + nsISupports* identity = nativeHelper.GetCanonical(); + + // The object should specify that it's meant to be global. + MOZ_ASSERT(nativeHelper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT); + + // We shouldn't be reusing globals. + MOZ_ASSERT(!nativeHelper.GetWrapperCache() || + !nativeHelper.GetWrapperCache()->GetWrapperPreserveColor()); + + // Put together the ScriptableCreateInfo... + XPCNativeScriptableCreateInfo sciProto; + XPCNativeScriptableCreateInfo sciMaybe; + const XPCNativeScriptableCreateInfo& sciWrapper = + GatherScriptableCreateInfo(identity, nativeHelper.GetClassInfo(), + sciProto, sciMaybe); + + // ...and then ScriptableInfo. We need all this stuff now because it's going + // to tell us the JSClass of the object we're going to create. + XPCNativeScriptableInfo* si = XPCNativeScriptableInfo::Construct(&sciWrapper); + MOZ_ASSERT(si); + + // Finally, we get to the JSClass. + const JSClass* clasp = si->GetJSClass(); + MOZ_ASSERT(clasp->flags & JSCLASS_IS_GLOBAL); + + // Create the global. + aOptions.creationOptions().setTrace(XPCWrappedNative::Trace); + if (xpc::SharedMemoryEnabled()) + aOptions.creationOptions().setSharedMemoryAndAtomicsEnabled(true); + RootedObject global(cx, xpc::CreateGlobalObject(cx, clasp, principal, aOptions)); + if (!global) + return NS_ERROR_FAILURE; + XPCWrappedNativeScope* scope = CompartmentPrivate::Get(global)->scope; + + // Immediately enter the global's compartment, so that everything else we + // create ends up there. + JSAutoCompartment ac(cx, global); + + // If requested, initialize the standard classes on the global. + if (initStandardClasses && ! JS_InitStandardClasses(cx, global)) + return NS_ERROR_FAILURE; + + // Make a proto. + XPCWrappedNativeProto* proto = + XPCWrappedNativeProto::GetNewOrUsed(scope, + nativeHelper.GetClassInfo(), &sciProto, + /* callPostCreatePrototype = */ false); + if (!proto) + return NS_ERROR_FAILURE; + + // Set up the prototype on the global. + MOZ_ASSERT(proto->GetJSProtoObject()); + RootedObject protoObj(cx, proto->GetJSProtoObject()); + bool success = JS_SplicePrototype(cx, global, protoObj); + if (!success) + return NS_ERROR_FAILURE; + + // Construct the wrapper, which takes over the strong reference to the + // native object. + RefPtr wrapper = + new XPCWrappedNative(nativeHelper.forgetCanonical(), proto); + + // + // We don't call ::Init() on this wrapper, because our setup requirements + // are different for globals. We do our setup inline here, instead. + // + + // Share mScriptableInfo with the proto. + // + // This is probably more trouble than it's worth, since we've already + // created an XPCNativeScriptableInfo for ourselves. Nevertheless, this is + // what ::Init() does, and we want to be as consistent as possible with + // that code. + XPCNativeScriptableInfo* siProto = proto->GetScriptableInfo(); + if (siProto && siProto->GetCallback() == sciWrapper.GetCallback()) { + wrapper->mScriptableInfo = siProto; + // XPCNativeScriptableInfo uses manual memory management. If we're + // switching over to that of the proto, we need to destroy the one + // we've allocated. + delete si; + si = nullptr; + } else { + wrapper->mScriptableInfo = si; + } + + // Set the JS object to the global we already created. + wrapper->mFlatJSObject = global; + wrapper->mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); + + // Set the private to the XPCWrappedNative. + JS_SetPrivate(global, wrapper); + + // There are dire comments elsewhere in the code about how a GC can + // happen somewhere after wrapper initialization but before the wrapper is + // added to the hashtable in FinishCreate(). It's not clear if that can + // happen here, but let's just be safe for now. + AutoMarkingWrappedNativePtr wrapperMarker(cx, wrapper); + + // Call the common Init finish routine. This mainly just does an AddRef + // on behalf of XPConnect (the corresponding Release is in the finalizer + // hook), but it does some other miscellaneous things too, so we don't + // inline it. + success = wrapper->FinishInit(); + MOZ_ASSERT(success); + + // Go through some extra work to find the tearoff. This is kind of silly + // on a conceptual level: the point of tearoffs is to cache the results + // of QI-ing mIdentity to different interfaces, and we don't need that + // since we're dealing with nsISupports. But lots of code expects tearoffs + // to exist for everything, so we just follow along. + RefPtr iface = XPCNativeInterface::GetNewOrUsed(&NS_GET_IID(nsISupports)); + MOZ_ASSERT(iface); + nsresult status; + success = wrapper->FindTearOff(iface, false, &status); + if (!success) + return status; + + // Call the common creation finish routine. This does all of the bookkeeping + // like inserting the wrapper into the wrapper map and setting up the wrapper + // cache. + nsresult rv = FinishCreate(scope, iface, nativeHelper.GetWrapperCache(), + wrapper, wrappedGlobal); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +// static +nsresult +XPCWrappedNative::GetNewOrUsed(xpcObjectHelper& helper, + XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + XPCWrappedNative** resultWrapper) +{ + MOZ_ASSERT(Interface); + AutoJSContext cx; + nsWrapperCache* cache = helper.GetWrapperCache(); + + MOZ_ASSERT(!cache || !cache->GetWrapperPreserveColor(), + "We assume the caller already checked if it could get the " + "wrapper from the cache."); + + nsresult rv; + + MOZ_ASSERT(!Scope->GetContext()->GCIsRunning(), + "XPCWrappedNative::GetNewOrUsed called during GC"); + + nsISupports* identity = helper.GetCanonical(); + + if (!identity) { + NS_ERROR("This XPCOM object fails in QueryInterface to nsISupports!"); + return NS_ERROR_FAILURE; + } + + RefPtr wrapper; + + Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); + // Some things are nsWrapperCache subclasses but never use the cache, so go + // ahead and check our map even if we have a cache and it has no existing + // wrapper: we might have an XPCWrappedNative anyway. + wrapper = map->Find(identity); + + if (wrapper) { + if (!wrapper->FindTearOff(Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + wrapper.forget(resultWrapper); + return NS_OK; + } + + // There is a chance that the object wants to have the self-same JSObject + // reflection regardless of the scope into which we are reflecting it. + // Many DOM objects require this. The scriptable helper specifies this + // in preCreate by indicating a 'parent' of a particular scope. + // + // To handle this we need to get the scriptable helper early and ask it. + // It is possible that we will then end up forwarding this entire call + // to this same function but with a different scope. + + // If we are making a wrapper for an nsIClassInfo singleton then + // We *don't* want to have it use the prototype meant for instances + // of that class. + uint32_t classInfoFlags; + bool isClassInfoSingleton = helper.GetClassInfo() == helper.Object() && + NS_SUCCEEDED(helper.GetClassInfo() + ->GetFlags(&classInfoFlags)) && + (classInfoFlags & nsIClassInfo::SINGLETON_CLASSINFO); + + nsIClassInfo* info = helper.GetClassInfo(); + + XPCNativeScriptableCreateInfo sciProto; + XPCNativeScriptableCreateInfo sci; + + // Gather scriptable create info if we are wrapping something + // other than an nsIClassInfo object. We need to not do this for + // nsIClassInfo objects because often nsIClassInfo implementations + // are also nsIXPCScriptable helper implementations, but the helper + // code is obviously intended for the implementation of the class + // described by the nsIClassInfo, not for the class info object + // itself. + const XPCNativeScriptableCreateInfo& sciWrapper = + isClassInfoSingleton ? sci : + GatherScriptableCreateInfo(identity, info, sciProto, sci); + + RootedObject parent(cx, Scope->GetGlobalJSObject()); + + mozilla::Maybe ac; + + if (sciWrapper.GetFlags().WantPreCreate()) { + RootedObject plannedParent(cx, parent); + nsresult rv = sciWrapper.GetCallback()->PreCreate(identity, cx, + parent, parent.address()); + if (NS_FAILED(rv)) + return rv; + rv = NS_OK; + + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent), + "Xray wrapper being used to parent XPCWrappedNative?"); + + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(parent) == parent, + "Non-global being used to parent XPCWrappedNative?"); + + ac.emplace(static_cast(cx), parent); + + if (parent != plannedParent) { + XPCWrappedNativeScope* betterScope = ObjectScope(parent); + MOZ_ASSERT(betterScope != Scope, + "How can we have the same scope for two different globals?"); + return GetNewOrUsed(helper, betterScope, Interface, resultWrapper); + } + + // Take the performance hit of checking the hashtable again in case + // the preCreate call caused the wrapper to get created through some + // interesting path (the DOM code tends to make this happen sometimes). + + if (cache) { + RootedObject cached(cx, cache->GetWrapper()); + if (cached) + wrapper = XPCWrappedNative::Get(cached); + } else { + wrapper = map->Find(identity); + } + + if (wrapper) { + if (wrapper->FindTearOff(Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + wrapper.forget(resultWrapper); + return NS_OK; + } + } else { + ac.emplace(static_cast(cx), parent); + } + + AutoMarkingWrappedNativeProtoPtr proto(cx); + + // If there is ClassInfo (and we are not building a wrapper for the + // nsIClassInfo interface) then we use a wrapper that needs a prototype. + + // Note that the security check happens inside FindTearOff - after the + // wrapper is actually created, but before JS code can see it. + + if (info && !isClassInfoSingleton) { + proto = XPCWrappedNativeProto::GetNewOrUsed(Scope, info, &sciProto); + if (!proto) + return NS_ERROR_FAILURE; + + wrapper = new XPCWrappedNative(helper.forgetCanonical(), proto); + } else { + RefPtr iface = Interface; + if (!iface) + iface = XPCNativeInterface::GetISupports(); + + XPCNativeSetKey key(iface); + RefPtr set = + XPCNativeSet::GetNewOrUsed(&key); + + if (!set) + return NS_ERROR_FAILURE; + + wrapper = new XPCWrappedNative(helper.forgetCanonical(), Scope, + set.forget()); + } + + MOZ_ASSERT(!xpc::WrapperFactory::IsXrayWrapper(parent), + "Xray wrapper being used to parent XPCWrappedNative?"); + + // We use an AutoMarkingPtr here because it is possible for JS gc to happen + // after we have Init'd the wrapper but *before* we add it to the hashtable. + // This would cause the mSet to get collected and we'd later crash. I've + // *seen* this happen. + AutoMarkingWrappedNativePtr wrapperMarker(cx, wrapper); + + if (!wrapper->Init(&sciWrapper)) + return NS_ERROR_FAILURE; + + if (!wrapper->FindTearOff(Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + + return FinishCreate(Scope, Interface, cache, wrapper, resultWrapper); +} + +static nsresult +FinishCreate(XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + nsWrapperCache* cache, + XPCWrappedNative* inWrapper, + XPCWrappedNative** resultWrapper) +{ + AutoJSContext cx; + MOZ_ASSERT(inWrapper); + + Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); + + RefPtr wrapper; + // Deal with the case where the wrapper got created as a side effect + // of one of our calls out of this code. Add() returns the (possibly + // pre-existing) wrapper that ultimately ends up in the map, which is + // what we want. + wrapper = map->Add(inWrapper); + if (!wrapper) + return NS_ERROR_FAILURE; + + if (wrapper == inWrapper) { + JSObject* flat = wrapper->GetFlatJSObject(); + MOZ_ASSERT(!cache || !cache->GetWrapperPreserveColor() || + flat == cache->GetWrapperPreserveColor(), + "This object has a cached wrapper that's different from " + "the JSObject held by its native wrapper?"); + + if (cache && !cache->GetWrapperPreserveColor()) + cache->SetWrapper(flat); + } + + DEBUG_CheckClassInfoClaims(wrapper); + wrapper.forget(resultWrapper); + return NS_OK; +} + +// static +nsresult +XPCWrappedNative::GetUsedOnly(nsISupports* Object, + XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + XPCWrappedNative** resultWrapper) +{ + AutoJSContext cx; + MOZ_ASSERT(Object, "XPCWrappedNative::GetUsedOnly was called with a null Object"); + MOZ_ASSERT(Interface); + + RefPtr wrapper; + nsWrapperCache* cache = nullptr; + CallQueryInterface(Object, &cache); + if (cache) { + RootedObject flat(cx, cache->GetWrapper()); + if (!flat) { + *resultWrapper = nullptr; + return NS_OK; + } + wrapper = XPCWrappedNative::Get(flat); + } else { + nsCOMPtr identity = do_QueryInterface(Object); + + if (!identity) { + NS_ERROR("This XPCOM object fails in QueryInterface to nsISupports!"); + return NS_ERROR_FAILURE; + } + + Native2WrappedNativeMap* map = Scope->GetWrappedNativeMap(); + + wrapper = map->Find(identity); + if (!wrapper) { + *resultWrapper = nullptr; + return NS_OK; + } + } + + nsresult rv; + if (!wrapper->FindTearOff(Interface, false, &rv)) { + MOZ_ASSERT(NS_FAILED(rv), "returning NS_OK on failure"); + return rv; + } + + wrapper.forget(resultWrapper); + return NS_OK; +} + +// This ctor is used if this object will have a proto. +XPCWrappedNative::XPCWrappedNative(already_AddRefed&& aIdentity, + XPCWrappedNativeProto* aProto) + : mMaybeProto(aProto), + mSet(aProto->GetSet()), + mScriptableInfo(nullptr) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mIdentity = aIdentity; + mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); + + MOZ_ASSERT(mMaybeProto, "bad ctor param"); + MOZ_ASSERT(mSet, "bad ctor param"); +} + +// This ctor is used if this object will NOT have a proto. +XPCWrappedNative::XPCWrappedNative(already_AddRefed&& aIdentity, + XPCWrappedNativeScope* aScope, + already_AddRefed&& aSet) + + : mMaybeScope(TagScope(aScope)), + mSet(aSet), + mScriptableInfo(nullptr) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mIdentity = aIdentity; + mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); + + MOZ_ASSERT(aScope, "bad ctor param"); + MOZ_ASSERT(mSet, "bad ctor param"); +} + +XPCWrappedNative::~XPCWrappedNative() +{ + Destroy(); +} + +void +XPCWrappedNative::Destroy() +{ + XPCWrappedNativeProto* proto = GetProto(); + + if (mScriptableInfo && + (!HasProto() || + (proto && proto->GetScriptableInfo() != mScriptableInfo))) { + delete mScriptableInfo; + mScriptableInfo = nullptr; + } + + XPCWrappedNativeScope* scope = GetScope(); + if (scope) { + Native2WrappedNativeMap* map = scope->GetWrappedNativeMap(); + + // Post-1.9 we should not remove this wrapper from the map if it is + // uninitialized. + map->Remove(this); + } + + if (mIdentity) { + XPCJSContext* cx = GetContext(); + if (cx && cx->GetDoingFinalization()) { + DeferredFinalize(mIdentity.forget().take()); + } else { + mIdentity = nullptr; + } + } + + mMaybeScope = nullptr; +} + +void +XPCWrappedNative::UpdateScriptableInfo(XPCNativeScriptableInfo* si) +{ + MOZ_ASSERT(mScriptableInfo, "UpdateScriptableInfo expects an existing scriptable info"); + mScriptableInfo = si; +} + +void +XPCWrappedNative::SetProto(XPCWrappedNativeProto* p) +{ + MOZ_ASSERT(!IsWrapperExpired(), "bad ptr!"); + + MOZ_ASSERT(HasProto()); + + // Write barrier for incremental GC. + JSContext* cx = GetContext()->Context(); + GetProto()->WriteBarrierPre(cx); + + mMaybeProto = p; +} + +// This is factored out so that it can be called publicly +// static +void +XPCWrappedNative::GatherProtoScriptableCreateInfo(nsIClassInfo* classInfo, + XPCNativeScriptableCreateInfo& sciProto) +{ + MOZ_ASSERT(classInfo, "bad param"); + MOZ_ASSERT(!sciProto.GetCallback(), "bad param"); + + nsXPCClassInfo* classInfoHelper = nullptr; + CallQueryInterface(classInfo, &classInfoHelper); + if (classInfoHelper) { + nsCOMPtr helper = + dont_AddRef(static_cast(classInfoHelper)); + uint32_t flags = classInfoHelper->GetScriptableFlags(); + sciProto.SetCallback(helper.forget()); + sciProto.SetFlags(XPCNativeScriptableFlags(flags)); + + return; + } + + nsCOMPtr helper; + nsresult rv = classInfo->GetScriptableHelper(getter_AddRefs(helper)); + if (NS_SUCCEEDED(rv) && helper) { + uint32_t flags = helper->GetScriptableFlags(); + sciProto.SetCallback(helper.forget()); + sciProto.SetFlags(XPCNativeScriptableFlags(flags)); + } +} + +// static +const XPCNativeScriptableCreateInfo& +XPCWrappedNative::GatherScriptableCreateInfo(nsISupports* obj, + nsIClassInfo* classInfo, + XPCNativeScriptableCreateInfo& sciProto, + XPCNativeScriptableCreateInfo& sciWrapper) +{ + MOZ_ASSERT(!sciWrapper.GetCallback(), "bad param"); + + // Get the class scriptable helper (if present) + if (classInfo) { + GatherProtoScriptableCreateInfo(classInfo, sciProto); + + if (sciProto.GetFlags().DontAskInstanceForScriptable()) + return sciProto; + } + + // Do the same for the wrapper specific scriptable + nsCOMPtr helper(do_QueryInterface(obj)); + if (helper) { + uint32_t flags = helper->GetScriptableFlags(); + sciWrapper.SetCallback(helper.forget()); + sciWrapper.SetFlags(XPCNativeScriptableFlags(flags)); + + // A whole series of assertions to catch bad uses of scriptable flags on + // the siWrapper... + + MOZ_ASSERT(!(sciWrapper.GetFlags().WantPreCreate() && + !sciProto.GetFlags().WantPreCreate()), + "Can't set WANT_PRECREATE on an instance scriptable " + "without also setting it on the class scriptable"); + + MOZ_ASSERT(!(sciWrapper.GetFlags().DontEnumQueryInterface() && + !sciProto.GetFlags().DontEnumQueryInterface() && + sciProto.GetCallback()), + "Can't set DONT_ENUM_QUERY_INTERFACE on an instance scriptable " + "without also setting it on the class scriptable (if present and shared)"); + + MOZ_ASSERT(!(sciWrapper.GetFlags().DontAskInstanceForScriptable() && + !sciProto.GetFlags().DontAskInstanceForScriptable()), + "Can't set DONT_ASK_INSTANCE_FOR_SCRIPTABLE on an instance scriptable " + "without also setting it on the class scriptable"); + + MOZ_ASSERT(!(sciWrapper.GetFlags().ClassInfoInterfacesOnly() && + !sciProto.GetFlags().ClassInfoInterfacesOnly() && + sciProto.GetCallback()), + "Can't set CLASSINFO_INTERFACES_ONLY on an instance scriptable " + "without also setting it on the class scriptable (if present and shared)"); + + MOZ_ASSERT(!(sciWrapper.GetFlags().AllowPropModsDuringResolve() && + !sciProto.GetFlags().AllowPropModsDuringResolve() && + sciProto.GetCallback()), + "Can't set ALLOW_PROP_MODS_DURING_RESOLVE on an instance scriptable " + "without also setting it on the class scriptable (if present and shared)"); + + MOZ_ASSERT(!(sciWrapper.GetFlags().AllowPropModsToPrototype() && + !sciProto.GetFlags().AllowPropModsToPrototype() && + sciProto.GetCallback()), + "Can't set ALLOW_PROP_MODS_TO_PROTOTYPE on an instance scriptable " + "without also setting it on the class scriptable (if present and shared)"); + + return sciWrapper; + } + + return sciProto; +} + +bool +XPCWrappedNative::Init(const XPCNativeScriptableCreateInfo* sci) +{ + AutoJSContext cx; + // setup our scriptable info... + + if (sci->GetCallback()) { + if (HasProto()) { + XPCNativeScriptableInfo* siProto = GetProto()->GetScriptableInfo(); + if (siProto && siProto->GetCallback() == sci->GetCallback()) + mScriptableInfo = siProto; + } + if (!mScriptableInfo) { + mScriptableInfo = XPCNativeScriptableInfo::Construct(sci); + + if (!mScriptableInfo) + return false; + } + } + XPCNativeScriptableInfo* si = mScriptableInfo; + + // create our flatJSObject + + const JSClass* jsclazz = si ? si->GetJSClass() : Jsvalify(&XPC_WN_NoHelper_JSClass); + + // We should have the global jsclass flag if and only if we're a global. + MOZ_ASSERT_IF(si, !!si->GetFlags().IsGlobalObject() == !!(jsclazz->flags & JSCLASS_IS_GLOBAL)); + + MOZ_ASSERT(jsclazz && + jsclazz->name && + jsclazz->flags && + jsclazz->getResolve() && + jsclazz->hasFinalize(), "bad class"); + + // XXXbz JS_GetObjectPrototype wants an object, even though it then asserts + // that this object is same-compartment with cx, which means it could just + // use the cx global... + RootedObject global(cx, CurrentGlobalOrNull(cx)); + RootedObject protoJSObject(cx, HasProto() ? + GetProto()->GetJSProtoObject() : + JS_GetObjectPrototype(cx, global)); + if (!protoJSObject) { + return false; + } + + mFlatJSObject = JS_NewObjectWithGivenProto(cx, jsclazz, protoJSObject); + if (!mFlatJSObject) { + mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); + return false; + } + + mFlatJSObject.setFlags(FLAT_JS_OBJECT_VALID); + JS_SetPrivate(mFlatJSObject, this); + + return FinishInit(); +} + +bool +XPCWrappedNative::FinishInit() +{ + AutoJSContext cx; + + // This reference will be released when mFlatJSObject is finalized. + // Since this reference will push the refcount to 2 it will also root + // mFlatJSObject; + MOZ_ASSERT(1 == mRefCnt, "unexpected refcount value"); + NS_ADDREF(this); + + // A hack for bug 517665, increase the probability for GC. + JS_updateMallocCounter(cx, 2 * sizeof(XPCWrappedNative)); + + return true; +} + + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPCWrappedNative) + NS_INTERFACE_MAP_ENTRY(nsIXPConnectWrappedNative) + NS_INTERFACE_MAP_ENTRY(nsIXPConnectJSObjectHolder) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPConnectWrappedNative) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(XPCWrappedNative) + +// Release calls Destroy() immediately when the refcount drops to 0 to +// clear the weak references nsXPConnect has to XPCWNs and to ensure there +// are no pointers to dying protos. +NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(XPCWrappedNative, Destroy()) + +/* + * Wrapped Native lifetime management is messy! + * + * - At creation we push the refcount to 2 (only one of which is owned by + * the native caller that caused the wrapper creation). + * - During the JS GC Mark phase we mark any wrapper with a refcount > 1. + * - The *only* thing that can make the wrapper get destroyed is the + * finalization of mFlatJSObject. And *that* should only happen if the only + * reference is the single extra (internal) reference we hold. + * + * - The wrapper has a pointer to the nsISupports 'view' of the wrapped native + * object i.e... mIdentity. This is held until the wrapper's refcount goes + * to zero and the wrapper is released, or until an expired wrapper (i.e., + * one unlinked by the cycle collector) has had its JS object finalized. + * + * - The wrapper also has 'tearoffs'. It has one tearoff for each interface + * that is actually used on the native object. 'Used' means we have either + * needed to QueryInterface to verify the availability of that interface + * of that we've had to QueryInterface in order to actually make a call + * into the wrapped object via the pointer for the given interface. + * + * - Each tearoff's 'mNative' member (if non-null) indicates one reference + * held by our wrapper on the wrapped native for the given interface + * associated with the tearoff. If we release that reference then we set + * the tearoff's 'mNative' to null. + * + * - We use the occasion of the JavaScript GCCallback for the JSGC_MARK_END + * event to scan the tearoffs of all wrappers for non-null mNative members + * that represent unused references. We can tell that a given tearoff's + * mNative is unused by noting that no live XPCCallContexts hold a pointer + * to the tearoff. + * + * - As a time/space tradeoff we may decide to not do this scanning on + * *every* JavaScript GC. We *do* want to do this *sometimes* because + * we want to allow for wrapped native's to do their own tearoff patterns. + * So, we want to avoid holding references to interfaces that we don't need. + * At the same time, we don't want to be bracketing every call into a + * wrapped native object with a QueryInterface/Release pair. And we *never* + * make a call into the object except via the correct interface for which + * we've QI'd. + * + * - Each tearoff *can* have a mJSObject whose lazily resolved properties + * represent the methods/attributes/constants of that specific interface. + * This is optionally reflected into JavaScript as "foo.nsIFoo" when "foo" + * is the name of mFlatJSObject and "nsIFoo" is the name of the given + * interface associated with the tearoff. When we create the tearoff's + * mJSObject we set it's parent to be mFlatJSObject. This way we know that + * when mFlatJSObject get's collected there are no outstanding reachable + * tearoff mJSObjects. Note that we must clear the private of any lingering + * mJSObjects at this point because we have no guarentee of the *order* of + * finalization within a given gc cycle. + */ + +void +XPCWrappedNative::FlatJSObjectFinalized() +{ + if (!IsValid()) + return; + + // Iterate the tearoffs and null out each of their JSObject's privates. + // This will keep them from trying to access their pointers to the + // dying tearoff object. We can safely assume that those remaining + // JSObjects are about to be finalized too. + + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) { + JSObject* jso = to->GetJSObjectPreserveColor(); + if (jso) { + JS_SetPrivate(jso, nullptr); +#ifdef DEBUG + JS_UpdateWeakPointerAfterGCUnbarriered(&jso); + MOZ_ASSERT(!jso); +#endif + to->JSObjectFinalized(); + } + + // We also need to release any native pointers held... + RefPtr native = to->TakeNative(); + if (native && GetContext()) { + DeferredFinalize(native.forget().take()); + } + + to->SetInterface(nullptr); + } + + nsWrapperCache* cache = nullptr; + CallQueryInterface(mIdentity, &cache); + if (cache) + cache->ClearWrapper(); + + mFlatJSObject = nullptr; + mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); + + MOZ_ASSERT(mIdentity, "bad pointer!"); +#ifdef XP_WIN + // Try to detect free'd pointer + MOZ_ASSERT(*(int*)mIdentity.get() != 0xdddddddd, "bad pointer!"); + MOZ_ASSERT(*(int*)mIdentity.get() != 0, "bad pointer!"); +#endif + + if (IsWrapperExpired()) { + Destroy(); + } + + // Note that it's not safe to touch mNativeWrapper here since it's + // likely that it has already been finalized. + + Release(); +} + +void +XPCWrappedNative::FlatJSObjectMoved(JSObject* obj, const JSObject* old) +{ + JS::AutoAssertGCCallback inCallback(obj); + MOZ_ASSERT(mFlatJSObject.unbarrieredGetPtr() == old); + + nsWrapperCache* cache = nullptr; + CallQueryInterface(mIdentity, &cache); + if (cache) + cache->UpdateWrapper(obj, old); + + mFlatJSObject = obj; +} + +void +XPCWrappedNative::SystemIsBeingShutDown() +{ + if (!IsValid()) + return; + + // The long standing strategy is to leak some objects still held at shutdown. + // The general problem is that propagating release out of xpconnect at + // shutdown time causes a world of problems. + + // We leak mIdentity (see above). + + // Short circuit future finalization. + JS_SetPrivate(mFlatJSObject, nullptr); + mFlatJSObject = nullptr; + mFlatJSObject.unsetFlags(FLAT_JS_OBJECT_VALID); + + XPCWrappedNativeProto* proto = GetProto(); + + if (HasProto()) + proto->SystemIsBeingShutDown(); + + // We don't destroy mScriptableInfo here. The destructor will do it. + + // Cleanup the tearoffs. + for (XPCWrappedNativeTearOff* to = &mFirstTearOff; to; to = to->GetNextTearOff()) { + if (JSObject* jso = to->GetJSObjectPreserveColor()) { + JS_SetPrivate(jso, nullptr); + to->SetJSObject(nullptr); + } + // We leak the tearoff mNative + // (for the same reason we leak mIdentity - see above). + Unused << to->TakeNative().take(); + to->SetInterface(nullptr); + } +} + +/***************************************************************************/ + +// Dynamically ensure that two objects don't end up with the same private. +class MOZ_STACK_CLASS AutoClonePrivateGuard { +public: + AutoClonePrivateGuard(JSContext* cx, JSObject* aOld, JSObject* aNew) + : mOldReflector(cx, aOld), mNewReflector(cx, aNew) + { + MOZ_ASSERT(JS_GetPrivate(aOld) == JS_GetPrivate(aNew)); + } + + ~AutoClonePrivateGuard() + { + if (JS_GetPrivate(mOldReflector)) { + JS_SetPrivate(mNewReflector, nullptr); + } + } + +private: + RootedObject mOldReflector; + RootedObject mNewReflector; +}; + +bool +XPCWrappedNative::ExtendSet(XPCNativeInterface* aInterface) +{ + if (!mSet->HasInterface(aInterface)) { + XPCNativeSetKey key(mSet, aInterface); + RefPtr newSet = + XPCNativeSet::GetNewOrUsed(&key); + if (!newSet) + return false; + + mSet = newSet.forget(); + } + return true; +} + +XPCWrappedNativeTearOff* +XPCWrappedNative::FindTearOff(XPCNativeInterface* aInterface, + bool needJSObject /* = false */, + nsresult* pError /* = nullptr */) +{ + AutoJSContext cx; + nsresult rv = NS_OK; + XPCWrappedNativeTearOff* to; + XPCWrappedNativeTearOff* firstAvailable = nullptr; + + XPCWrappedNativeTearOff* lastTearOff; + for (lastTearOff = to = &mFirstTearOff; + to; + lastTearOff = to, to = to->GetNextTearOff()) { + if (to->GetInterface() == aInterface) { + if (needJSObject && !to->GetJSObjectPreserveColor()) { + AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to); + bool ok = InitTearOffJSObject(to); + // During shutdown, we don't sweep tearoffs. So make sure + // to unmark manually in case the auto-marker marked us. + // We shouldn't ever be getting here _during_ our + // Mark/Sweep cycle, so this should be safe. + to->Unmark(); + if (!ok) { + to = nullptr; + rv = NS_ERROR_OUT_OF_MEMORY; + } + } + if (pError) + *pError = rv; + return to; + } + if (!firstAvailable && to->IsAvailable()) + firstAvailable = to; + } + + to = firstAvailable; + + if (!to) { + to = lastTearOff->AddTearOff(); + } + + { + // Scope keeps |tearoff| from leaking across the rest of the function. + AutoMarkingWrappedNativeTearOffPtr tearoff(cx, to); + rv = InitTearOff(to, aInterface, needJSObject); + // During shutdown, we don't sweep tearoffs. So make sure to unmark + // manually in case the auto-marker marked us. We shouldn't ever be + // getting here _during_ our Mark/Sweep cycle, so this should be safe. + to->Unmark(); + if (NS_FAILED(rv)) + to = nullptr; + } + + if (pError) + *pError = rv; + return to; +} + +XPCWrappedNativeTearOff* +XPCWrappedNative::FindTearOff(const nsIID& iid) { + RefPtr iface = XPCNativeInterface::GetNewOrUsed(&iid); + return iface ? FindTearOff(iface) : nullptr; +} + +nsresult +XPCWrappedNative::InitTearOff(XPCWrappedNativeTearOff* aTearOff, + XPCNativeInterface* aInterface, + bool needJSObject) +{ + AutoJSContext cx; + + // Determine if the object really does this interface... + + const nsIID* iid = aInterface->GetIID(); + nsISupports* identity = GetIdentityObject(); + + // This is an nsRefPtr instead of an nsCOMPtr because it may not be the + // canonical nsISupports for this object. + RefPtr qiResult; + + // If the scriptable helper forbids us from reflecting additional + // interfaces, then don't even try the QI, just fail. + if (mScriptableInfo && + mScriptableInfo->GetFlags().ClassInfoInterfacesOnly() && + !mSet->HasInterface(aInterface) && + !mSet->HasInterfaceWithAncestor(aInterface)) { + return NS_ERROR_NO_INTERFACE; + } + + // We are about to call out to other code. + // So protect our intended tearoff. + + aTearOff->SetReserved(); + + if (NS_FAILED(identity->QueryInterface(*iid, getter_AddRefs(qiResult))) || !qiResult) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + + // Guard against trying to build a tearoff for a shared nsIClassInfo. + if (iid->Equals(NS_GET_IID(nsIClassInfo))) { + nsCOMPtr alternate_identity(do_QueryInterface(qiResult)); + if (alternate_identity.get() != identity) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + } + + // Guard against trying to build a tearoff for an interface that is + // aggregated and is implemented as a nsIXPConnectWrappedJS using this + // self-same JSObject. The XBL system does this. If we mutate the set + // of this wrapper then we will shadow the method that XBL has added to + // the JSObject that it has inserted in the JS proto chain between our + // JSObject and our XPCWrappedNativeProto's JSObject. If we let this + // set mutation happen then the interface's methods will be added to + // our JSObject, but calls on those methods will get routed up to + // native code and into the wrappedJS - which will do a method lookup + // on *our* JSObject and find the same method and make another call + // into an infinite loop. + // see: http://bugzilla.mozilla.org/show_bug.cgi?id=96725 + + // The code in this block also does a check for the double wrapped + // nsIPropertyBag case. + + nsCOMPtr wrappedJS(do_QueryInterface(qiResult)); + if (wrappedJS) { + RootedObject jso(cx, wrappedJS->GetJSObject()); + if (jso == mFlatJSObject) { + // The implementing JSObject is the same as ours! Just say OK + // without actually extending the set. + // + // XXX It is a little cheesy to have FindTearOff return an + // 'empty' tearoff. But this is the centralized place to do the + // QI activities on the underlying object. *And* most caller to + // FindTearOff only look for a non-null result and ignore the + // actual tearoff returned. The only callers that do use the + // returned tearoff make sure to check for either a non-null + // JSObject or a matching Interface before proceeding. + // I think we can get away with this bit of ugliness. + + aTearOff->SetInterface(nullptr); + return NS_OK; + } + + // Decide whether or not to expose nsIPropertyBag to calling + // JS code in the double wrapped case. + // + // Our rule here is that when JSObjects are double wrapped and + // exposed to other JSObjects then the nsIPropertyBag interface + // is only exposed on an 'opt-in' basis; i.e. if the underlying + // JSObject wants other JSObjects to be able to see this interface + // then it must implement QueryInterface and not throw an exception + // when asked for nsIPropertyBag. It need not actually *implement* + // nsIPropertyBag - xpconnect will do that work. + + if (iid->Equals(NS_GET_IID(nsIPropertyBag)) && jso) { + RefPtr clasp = nsXPCWrappedJSClass::GetNewOrUsed(cx, *iid); + if (clasp) { + RootedObject answer(cx, clasp->CallQueryInterfaceOnJSObject(cx, jso, *iid)); + + if (!answer) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + } + } + } + + if (NS_FAILED(nsXPConnect::SecurityManager()->CanCreateWrapper(cx, *iid, identity, + GetClassInfo()))) { + // the security manager vetoed. It should have set an exception. + aTearOff->SetInterface(nullptr); + return NS_ERROR_XPC_SECURITY_MANAGER_VETO; + } + + // If this is not already in our set we need to extend our set. + // Note: we do not cache the result of the previous call to HasInterface() + // because we unlocked and called out in the interim and the result of the + // previous call might not be correct anymore. + + if (!mSet->HasInterface(aInterface) && !ExtendSet(aInterface)) { + aTearOff->SetInterface(nullptr); + return NS_ERROR_NO_INTERFACE; + } + + aTearOff->SetInterface(aInterface); + aTearOff->SetNative(qiResult); + if (needJSObject && !InitTearOffJSObject(aTearOff)) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +bool +XPCWrappedNative::InitTearOffJSObject(XPCWrappedNativeTearOff* to) +{ + AutoJSContext cx; + + JSObject* obj = JS_NewObject(cx, Jsvalify(&XPC_WN_Tearoff_JSClass)); + if (!obj) + return false; + + JS_SetPrivate(obj, to); + to->SetJSObject(obj); + + js::SetReservedSlot(obj, XPC_WN_TEAROFF_FLAT_OBJECT_SLOT, + JS::ObjectValue(*mFlatJSObject)); + return true; +} + +/***************************************************************************/ + +static bool Throw(nsresult errNum, XPCCallContext& ccx) +{ + XPCThrower::Throw(errNum, ccx); + return false; +} + +/***************************************************************************/ + +class MOZ_STACK_CLASS CallMethodHelper +{ + XPCCallContext& mCallContext; + nsresult mInvokeResult; + nsIInterfaceInfo* const mIFaceInfo; + const nsXPTMethodInfo* mMethodInfo; + nsISupports* const mCallee; + const uint16_t mVTableIndex; + HandleId mIdxValueId; + + AutoTArray mDispatchParams; + uint8_t mJSContextIndex; // TODO make const + uint8_t mOptArgcIndex; // TODO make const + + Value* const mArgv; + const uint32_t mArgc; + + MOZ_ALWAYS_INLINE bool + GetArraySizeFromParam(uint8_t paramIndex, HandleValue maybeArray, uint32_t* result); + + MOZ_ALWAYS_INLINE bool + GetInterfaceTypeFromParam(uint8_t paramIndex, + const nsXPTType& datum_type, + nsID* result) const; + + MOZ_ALWAYS_INLINE bool + GetOutParamSource(uint8_t paramIndex, MutableHandleValue srcp) const; + + MOZ_ALWAYS_INLINE bool + GatherAndConvertResults(); + + MOZ_ALWAYS_INLINE bool + QueryInterfaceFastPath(); + + nsXPTCVariant* + GetDispatchParam(uint8_t paramIndex) + { + if (paramIndex >= mJSContextIndex) + paramIndex += 1; + if (paramIndex >= mOptArgcIndex) + paramIndex += 1; + return &mDispatchParams[paramIndex]; + } + const nsXPTCVariant* + GetDispatchParam(uint8_t paramIndex) const + { + return const_cast(this)->GetDispatchParam(paramIndex); + } + + MOZ_ALWAYS_INLINE bool InitializeDispatchParams(); + + MOZ_ALWAYS_INLINE bool ConvertIndependentParams(bool* foundDependentParam); + MOZ_ALWAYS_INLINE bool ConvertIndependentParam(uint8_t i); + MOZ_ALWAYS_INLINE bool ConvertDependentParams(); + MOZ_ALWAYS_INLINE bool ConvertDependentParam(uint8_t i); + + MOZ_ALWAYS_INLINE void CleanupParam(nsXPTCMiniVariant& param, nsXPTType& type); + + MOZ_ALWAYS_INLINE bool AllocateStringClass(nsXPTCVariant* dp, + const nsXPTParamInfo& paramInfo); + + MOZ_ALWAYS_INLINE nsresult Invoke(); + +public: + + explicit CallMethodHelper(XPCCallContext& ccx) + : mCallContext(ccx) + , mInvokeResult(NS_ERROR_UNEXPECTED) + , mIFaceInfo(ccx.GetInterface()->GetInterfaceInfo()) + , mMethodInfo(nullptr) + , mCallee(ccx.GetTearOff()->GetNative()) + , mVTableIndex(ccx.GetMethodIndex()) + , mIdxValueId(ccx.GetContext()->GetStringID(XPCJSContext::IDX_VALUE)) + , mJSContextIndex(UINT8_MAX) + , mOptArgcIndex(UINT8_MAX) + , mArgv(ccx.GetArgv()) + , mArgc(ccx.GetArgc()) + + { + // Success checked later. + mIFaceInfo->GetMethodInfo(mVTableIndex, &mMethodInfo); + } + + ~CallMethodHelper(); + + MOZ_ALWAYS_INLINE bool Call(); + +}; + +// static +bool +XPCWrappedNative::CallMethod(XPCCallContext& ccx, + CallMode mode /*= CALL_METHOD */) +{ + nsresult rv = ccx.CanCallNow(); + if (NS_FAILED(rv)) { + return Throw(rv, ccx); + } + + return CallMethodHelper(ccx).Call(); +} + +bool +CallMethodHelper::Call() +{ + mCallContext.SetRetVal(JS::UndefinedValue()); + + XPCJSContext::Get()->SetPendingException(nullptr); + + if (mVTableIndex == 0) { + return QueryInterfaceFastPath(); + } + + if (!mMethodInfo) { + Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, mCallContext); + return false; + } + + if (!InitializeDispatchParams()) + return false; + + // Iterate through the params doing conversions of independent params only. + // When we later convert the dependent params (if any) we will know that + // the params upon which they depend will have already been converted - + // regardless of ordering. + bool foundDependentParam = false; + if (!ConvertIndependentParams(&foundDependentParam)) + return false; + + if (foundDependentParam && !ConvertDependentParams()) + return false; + + mInvokeResult = Invoke(); + + if (JS_IsExceptionPending(mCallContext)) { + return false; + } + + if (NS_FAILED(mInvokeResult)) { + ThrowBadResult(mInvokeResult, mCallContext); + return false; + } + + return GatherAndConvertResults(); +} + +CallMethodHelper::~CallMethodHelper() +{ + uint8_t paramCount = mMethodInfo->GetParamCount(); + if (mDispatchParams.Length()) { + for (uint8_t i = 0; i < paramCount; i++) { + nsXPTCVariant* dp = GetDispatchParam(i); + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + + if (paramInfo.GetType().IsArray()) { + void* p = dp->val.p; + if (!p) + continue; + + // Clean up the array contents if necessary. + if (dp->DoesValNeedCleanup()) { + // We need some basic information to properly destroy the array. + uint32_t array_count = 0; + nsXPTType datum_type; + if (!GetArraySizeFromParam(i, UndefinedHandleValue, &array_count) || + !NS_SUCCEEDED(mIFaceInfo->GetTypeForParam(mVTableIndex, + ¶mInfo, + 1, &datum_type))) { + // XXXbholley - I'm not convinced that the above calls will + // ever fail. + NS_ERROR("failed to get array information, we'll leak here"); + continue; + } + + // Loop over the array contents. For each one, we create a + // dummy 'val' and pass it to the cleanup helper. + for (uint32_t k = 0; k < array_count; k++) { + nsXPTCMiniVariant v; + v.val.p = static_cast(p)[k]; + CleanupParam(v, datum_type); + } + } + + // always free the array itself + free(p); + } else { + // Clean up single parameters (if requested). + if (dp->DoesValNeedCleanup()) + CleanupParam(*dp, dp->type); + } + } + } +} + +bool +CallMethodHelper::GetArraySizeFromParam(uint8_t paramIndex, + HandleValue maybeArray, + uint32_t* result) +{ + nsresult rv; + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); + + // TODO fixup the various exceptions that are thrown + + rv = mIFaceInfo->GetSizeIsArgNumberForParam(mVTableIndex, ¶mInfo, 0, ¶mIndex); + if (NS_FAILED(rv)) + return Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); + + // If the array length wasn't passed, it might have been listed as optional. + // When converting arguments from JS to C++, we pass the array as |maybeArray|, + // and give ourselves the chance to infer the length. Once we have it, we stick + // it in the right slot so that we can find it again when cleaning up the params. + // from the array. + if (paramIndex >= mArgc && maybeArray.isObject()) { + MOZ_ASSERT(mMethodInfo->GetParam(paramIndex).IsOptional()); + RootedObject arrayOrNull(mCallContext, maybeArray.isObject() ? &maybeArray.toObject() + : nullptr); + + bool isArray; + if (!JS_IsArrayObject(mCallContext, maybeArray, &isArray) || + !isArray || + !JS_GetArrayLength(mCallContext, arrayOrNull, &GetDispatchParam(paramIndex)->val.u32)) + { + return Throw(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY, mCallContext); + } + } + + *result = GetDispatchParam(paramIndex)->val.u32; + + return true; +} + +bool +CallMethodHelper::GetInterfaceTypeFromParam(uint8_t paramIndex, + const nsXPTType& datum_type, + nsID* result) const +{ + nsresult rv; + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); + uint8_t tag = datum_type.TagPart(); + + // TODO fixup the various exceptions that are thrown + + if (tag == nsXPTType::T_INTERFACE) { + rv = mIFaceInfo->GetIIDForParamNoAlloc(mVTableIndex, ¶mInfo, result); + if (NS_FAILED(rv)) + return ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, + paramIndex, mCallContext); + } else if (tag == nsXPTType::T_INTERFACE_IS) { + rv = mIFaceInfo->GetInterfaceIsArgNumberForParam(mVTableIndex, ¶mInfo, + ¶mIndex); + if (NS_FAILED(rv)) + return Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); + + nsID* p = (nsID*) GetDispatchParam(paramIndex)->val.p; + if (!p) + return ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, + paramIndex, mCallContext); + *result = *p; + } + return true; +} + +bool +CallMethodHelper::GetOutParamSource(uint8_t paramIndex, MutableHandleValue srcp) const +{ + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(paramIndex); + + MOZ_ASSERT(!paramInfo.IsDipper(), "Dipper params are handled separately"); + if (paramInfo.IsOut() && !paramInfo.IsRetval()) { + MOZ_ASSERT(paramIndex < mArgc || paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + Value arg = paramIndex < mArgc ? mArgv[paramIndex] : JS::NullValue(); + if (paramIndex < mArgc) { + RootedObject obj(mCallContext); + if (!arg.isPrimitive()) + obj = &arg.toObject(); + if (!obj || !JS_GetPropertyById(mCallContext, obj, mIdxValueId, srcp)) { + // Explicitly passed in unusable value for out param. Note + // that if i >= mArgc we already know that |arg| is JS::NullValue(), + // and that's ok. + ThrowBadParam(NS_ERROR_XPC_NEED_OUT_OBJECT, paramIndex, + mCallContext); + return false; + } + } + } + + return true; +} + +bool +CallMethodHelper::GatherAndConvertResults() +{ + // now we iterate through the native params to gather and convert results + uint8_t paramCount = mMethodInfo->GetParamCount(); + for (uint8_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + if (!paramInfo.IsOut() && !paramInfo.IsDipper()) + continue; + + const nsXPTType& type = paramInfo.GetType(); + nsXPTCVariant* dp = GetDispatchParam(i); + RootedValue v(mCallContext, NullValue()); + uint32_t array_count = 0; + nsXPTType datum_type; + bool isArray = type.IsArray(); + bool isSizedString = isArray ? + false : + type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || + type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; + + if (isArray) { + if (NS_FAILED(mIFaceInfo->GetTypeForParam(mVTableIndex, ¶mInfo, 1, + &datum_type))) { + Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); + return false; + } + } else + datum_type = type; + + if (isArray || isSizedString) { + if (!GetArraySizeFromParam(i, UndefinedHandleValue, &array_count)) + return false; + } + + nsID param_iid; + if (datum_type.IsInterfacePointer() && + !GetInterfaceTypeFromParam(i, datum_type, ¶m_iid)) + return false; + + nsresult err; + if (isArray) { + if (!XPCConvert::NativeArray2JS(&v, (const void**)&dp->val, + datum_type, ¶m_iid, + array_count, &err)) { + // XXX need exception scheme for arrays to indicate bad element + ThrowBadParam(err, i, mCallContext); + return false; + } + } else if (isSizedString) { + if (!XPCConvert::NativeStringWithSize2JS(&v, + (const void*)&dp->val, + datum_type, + array_count, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + } else { + if (!XPCConvert::NativeData2JS(&v, &dp->val, datum_type, + ¶m_iid, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + } + + if (paramInfo.IsRetval()) { + mCallContext.SetRetVal(v); + } else if (i < mArgc) { + // we actually assured this before doing the invoke + MOZ_ASSERT(mArgv[i].isObject(), "out var is not object"); + RootedObject obj(mCallContext, &mArgv[i].toObject()); + if (!JS_SetPropertyById(mCallContext, obj, mIdxValueId, v)) { + ThrowBadParam(NS_ERROR_XPC_CANT_SET_OUT_VAL, i, mCallContext); + return false; + } + } else { + MOZ_ASSERT(paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + } + } + + return true; +} + +bool +CallMethodHelper::QueryInterfaceFastPath() +{ + MOZ_ASSERT(mVTableIndex == 0, + "Using the QI fast-path for a method other than QueryInterface"); + + if (mArgc < 1) { + Throw(NS_ERROR_XPC_NOT_ENOUGH_ARGS, mCallContext); + return false; + } + + if (!mArgv[0].isObject()) { + ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext); + return false; + } + + const nsID* iid = xpc_JSObjectToID(mCallContext, &mArgv[0].toObject()); + if (!iid) { + ThrowBadParam(NS_ERROR_XPC_BAD_CONVERT_JS, 0, mCallContext); + return false; + } + + nsISupports* qiresult = nullptr; + mInvokeResult = mCallee->QueryInterface(*iid, (void**) &qiresult); + + if (NS_FAILED(mInvokeResult)) { + ThrowBadResult(mInvokeResult, mCallContext); + return false; + } + + RootedValue v(mCallContext, NullValue()); + nsresult err; + bool success = + XPCConvert::NativeData2JS(&v, &qiresult, + nsXPTType::T_INTERFACE_IS, + iid, &err); + NS_IF_RELEASE(qiresult); + + if (!success) { + ThrowBadParam(err, 0, mCallContext); + return false; + } + + mCallContext.SetRetVal(v); + return true; +} + +bool +CallMethodHelper::InitializeDispatchParams() +{ + const uint8_t wantsOptArgc = mMethodInfo->WantsOptArgc() ? 1 : 0; + const uint8_t wantsJSContext = mMethodInfo->WantsContext() ? 1 : 0; + const uint8_t paramCount = mMethodInfo->GetParamCount(); + uint8_t requiredArgs = paramCount; + uint8_t hasRetval = 0; + + // XXX ASSUMES that retval is last arg. The xpidl compiler ensures this. + if (paramCount && mMethodInfo->GetParam(paramCount-1).IsRetval()) { + hasRetval = 1; + requiredArgs--; + } + + if (mArgc < requiredArgs || wantsOptArgc) { + if (wantsOptArgc) + mOptArgcIndex = requiredArgs; + + // skip over any optional arguments + while (requiredArgs && mMethodInfo->GetParam(requiredArgs-1).IsOptional()) + requiredArgs--; + + if (mArgc < requiredArgs) { + Throw(NS_ERROR_XPC_NOT_ENOUGH_ARGS, mCallContext); + return false; + } + } + + if (wantsJSContext) { + if (wantsOptArgc) + // Need to bump mOptArgcIndex up one here. + mJSContextIndex = mOptArgcIndex++; + else if (mMethodInfo->IsSetter() || mMethodInfo->IsGetter()) + // For attributes, we always put the JSContext* first. + mJSContextIndex = 0; + else + mJSContextIndex = paramCount - hasRetval; + } + + // iterate through the params to clear flags (for safe cleanup later) + for (uint8_t i = 0; i < paramCount + wantsJSContext + wantsOptArgc; i++) { + nsXPTCVariant* dp = mDispatchParams.AppendElement(); + dp->ClearFlags(); + dp->val.p = nullptr; + } + + // Fill in the JSContext argument + if (wantsJSContext) { + nsXPTCVariant* dp = &mDispatchParams[mJSContextIndex]; + dp->type = nsXPTType::T_VOID; + dp->val.p = mCallContext; + } + + // Fill in the optional_argc argument + if (wantsOptArgc) { + nsXPTCVariant* dp = &mDispatchParams[mOptArgcIndex]; + dp->type = nsXPTType::T_U8; + dp->val.u8 = std::min(mArgc, paramCount) - requiredArgs; + } + + return true; +} + +bool +CallMethodHelper::ConvertIndependentParams(bool* foundDependentParam) +{ + const uint8_t paramCount = mMethodInfo->GetParamCount(); + for (uint8_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + + if (paramInfo.GetType().IsDependent()) + *foundDependentParam = true; + else if (!ConvertIndependentParam(i)) + return false; + + } + + return true; +} + +bool +CallMethodHelper::ConvertIndependentParam(uint8_t i) +{ + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + const nsXPTType& type = paramInfo.GetType(); + uint8_t type_tag = type.TagPart(); + nsXPTCVariant* dp = GetDispatchParam(i); + dp->type = type; + MOZ_ASSERT(!paramInfo.IsShared(), "[shared] implies [noscript]!"); + + // String classes are always "in" - those that are marked "out" are converted + // by the XPIDL compiler to "in+dipper". See the note above IsDipper() in + // xptinfo.h. + // + // Also note that the fact that we bail out early for dipper parameters means + // that "inout" dipper parameters don't work - see bug 687612. + if (paramInfo.IsStringClass()) { + if (!AllocateStringClass(dp, paramInfo)) + return false; + if (paramInfo.IsDipper()) { + // We've allocated our string class explicitly, so we don't need + // to do any conversions on the incoming argument. However, we still + // need to verify that it's an object, so that we don't get surprised + // later on when trying to assign the result to .value. + if (i < mArgc && !mArgv[i].isObject()) { + ThrowBadParam(NS_ERROR_XPC_NEED_OUT_OBJECT, i, mCallContext); + return false; + } + return true; + } + } + + // Specify the correct storage/calling semantics. + if (paramInfo.IsIndirect()) + dp->SetIndirect(); + + // The JSVal proper is always stored within the 'val' union and passed + // indirectly, regardless of in/out-ness. + if (type_tag == nsXPTType::T_JSVAL) { + // Root the value. + dp->val.j.setUndefined(); + if (!js::AddRawValueRoot(mCallContext, &dp->val.j, "XPCWrappedNative::CallMethod param")) + return false; + } + + // Flag cleanup for anything that isn't self-contained. + if (!type.IsArithmetic()) + dp->SetValNeedsCleanup(); + + // Even if there's nothing to convert, we still need to examine the + // JSObject container for out-params. If it's null or otherwise invalid, + // we want to know before the call, rather than after. + // + // This is a no-op for 'in' params. + RootedValue src(mCallContext); + if (!GetOutParamSource(i, &src)) + return false; + + // All that's left to do is value conversion. Bail early if we don't need + // to do that. + if (!paramInfo.IsIn()) + return true; + + // We're definitely some variety of 'in' now, so there's something to + // convert. The source value for conversion depends on whether we're + // dealing with an 'in' or an 'inout' parameter. 'inout' was handled above, + // so all that's left is 'in'. + if (!paramInfo.IsOut()) { + // Handle the 'in' case. + MOZ_ASSERT(i < mArgc || paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + if (i < mArgc) + src = mArgv[i]; + else if (type_tag == nsXPTType::T_JSVAL) + src.setUndefined(); + else + src.setNull(); + } + + nsID param_iid; + if (type_tag == nsXPTType::T_INTERFACE && + NS_FAILED(mIFaceInfo->GetIIDForParamNoAlloc(mVTableIndex, ¶mInfo, + ¶m_iid))) { + ThrowBadParam(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO, i, mCallContext); + return false; + } + + // Don't allow CPOWs to be passed to native code (in case they try to cast + // to a concrete type). + if (src.isObject() && + jsipc::IsWrappedCPOW(&src.toObject()) && + type_tag == nsXPTType::T_INTERFACE && + !param_iid.Equals(NS_GET_IID(nsISupports))) + { + // Allow passing CPOWs to XPCWrappedJS. + nsCOMPtr wrappedJS(do_QueryInterface(mCallee)); + if (!wrappedJS) { + ThrowBadParam(NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE, i, mCallContext); + return false; + } + } + + nsresult err; + if (!XPCConvert::JSData2Native(&dp->val, src, type, ¶m_iid, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + + return true; +} + +bool +CallMethodHelper::ConvertDependentParams() +{ + const uint8_t paramCount = mMethodInfo->GetParamCount(); + for (uint8_t i = 0; i < paramCount; i++) { + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + + if (!paramInfo.GetType().IsDependent()) + continue; + if (!ConvertDependentParam(i)) + return false; + } + + return true; +} + +bool +CallMethodHelper::ConvertDependentParam(uint8_t i) +{ + const nsXPTParamInfo& paramInfo = mMethodInfo->GetParam(i); + const nsXPTType& type = paramInfo.GetType(); + nsXPTType datum_type; + uint32_t array_count = 0; + bool isArray = type.IsArray(); + + bool isSizedString = isArray ? + false : + type.TagPart() == nsXPTType::T_PSTRING_SIZE_IS || + type.TagPart() == nsXPTType::T_PWSTRING_SIZE_IS; + + nsXPTCVariant* dp = GetDispatchParam(i); + dp->type = type; + + if (isArray) { + if (NS_FAILED(mIFaceInfo->GetTypeForParam(mVTableIndex, ¶mInfo, 1, + &datum_type))) { + Throw(NS_ERROR_XPC_CANT_GET_ARRAY_INFO, mCallContext); + return false; + } + MOZ_ASSERT(datum_type.TagPart() != nsXPTType::T_JSVAL, + "Arrays of JSVals not currently supported - see bug 693337."); + } else { + datum_type = type; + } + + // Specify the correct storage/calling semantics. + if (paramInfo.IsIndirect()) + dp->SetIndirect(); + + // We have 3 possible type of dependent parameters: Arrays, Sized Strings, + // and iid_is Interface pointers. The latter two always need cleanup, and + // arrays need cleanup for all non-arithmetic types. Since the latter two + // cases also happen to be non-arithmetic, we can just inspect datum_type + // here. + if (!datum_type.IsArithmetic()) + dp->SetValNeedsCleanup(); + + // Even if there's nothing to convert, we still need to examine the + // JSObject container for out-params. If it's null or otherwise invalid, + // we want to know before the call, rather than after. + // + // This is a no-op for 'in' params. + RootedValue src(mCallContext); + if (!GetOutParamSource(i, &src)) + return false; + + // All that's left to do is value conversion. Bail early if we don't need + // to do that. + if (!paramInfo.IsIn()) + return true; + + // We're definitely some variety of 'in' now, so there's something to + // convert. The source value for conversion depends on whether we're + // dealing with an 'in' or an 'inout' parameter. 'inout' was handled above, + // so all that's left is 'in'. + if (!paramInfo.IsOut()) { + // Handle the 'in' case. + MOZ_ASSERT(i < mArgc || paramInfo.IsOptional(), + "Expected either enough arguments or an optional argument"); + src = i < mArgc ? mArgv[i] : JS::NullValue(); + } + + nsID param_iid; + if (datum_type.IsInterfacePointer() && + !GetInterfaceTypeFromParam(i, datum_type, ¶m_iid)) + return false; + + nsresult err; + + if (isArray || isSizedString) { + if (!GetArraySizeFromParam(i, src, &array_count)) + return false; + + if (isArray) { + if (array_count && + !XPCConvert::JSArray2Native((void**)&dp->val, src, + array_count, datum_type, ¶m_iid, + &err)) { + // XXX need exception scheme for arrays to indicate bad element + ThrowBadParam(err, i, mCallContext); + return false; + } + } else // if (isSizedString) + { + if (!XPCConvert::JSStringWithSize2Native((void*)&dp->val, + src, array_count, + datum_type, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + } + } else { + if (!XPCConvert::JSData2Native(&dp->val, src, type, + ¶m_iid, &err)) { + ThrowBadParam(err, i, mCallContext); + return false; + } + } + + return true; +} + +// Performs all necessary teardown on a parameter after method invocation. +// +// This method should only be called if the value in question was flagged +// for cleanup (ie, if dp->DoesValNeedCleanup()). +void +CallMethodHelper::CleanupParam(nsXPTCMiniVariant& param, nsXPTType& type) +{ + // We handle array elements, but not the arrays themselves. + MOZ_ASSERT(type.TagPart() != nsXPTType::T_ARRAY, "Can't handle arrays."); + + // Pointers may sometimes be null even if cleanup was requested. Combine + // the null checking for all the different types into one check here. + if (type.TagPart() != nsXPTType::T_JSVAL && param.val.p == nullptr) + return; + + switch (type.TagPart()) { + case nsXPTType::T_JSVAL: + js::RemoveRawValueRoot(mCallContext, (Value*)¶m.val); + break; + case nsXPTType::T_INTERFACE: + case nsXPTType::T_INTERFACE_IS: + ((nsISupports*)param.val.p)->Release(); + break; + case nsXPTType::T_ASTRING: + case nsXPTType::T_DOMSTRING: + nsXPConnect::GetContextInstance()->mScratchStrings.Destroy((nsString*)param.val.p); + break; + case nsXPTType::T_UTF8STRING: + case nsXPTType::T_CSTRING: + nsXPConnect::GetContextInstance()->mScratchCStrings.Destroy((nsCString*)param.val.p); + break; + default: + MOZ_ASSERT(!type.IsArithmetic(), "Cleanup requested on unexpected type."); + free(param.val.p); + break; + } +} + +bool +CallMethodHelper::AllocateStringClass(nsXPTCVariant* dp, + const nsXPTParamInfo& paramInfo) +{ + // Get something we can make comparisons with. + uint8_t type_tag = paramInfo.GetType().TagPart(); + + // There should be 4 cases, all strings. Verify that here. + MOZ_ASSERT(type_tag == nsXPTType::T_ASTRING || + type_tag == nsXPTType::T_DOMSTRING || + type_tag == nsXPTType::T_UTF8STRING || + type_tag == nsXPTType::T_CSTRING, + "Unexpected string class type!"); + + // ASTRING and DOMSTRING are very similar, and both use nsString. + // UTF8_STRING and CSTRING are also quite similar, and both use nsCString. + if (type_tag == nsXPTType::T_ASTRING || type_tag == nsXPTType::T_DOMSTRING) + dp->val.p = nsXPConnect::GetContextInstance()->mScratchStrings.Create(); + else + dp->val.p = nsXPConnect::GetContextInstance()->mScratchCStrings.Create(); + + // Check for OOM, in either case. + if (!dp->val.p) { + JS_ReportOutOfMemory(mCallContext); + return false; + } + + // We allocated, so we need to deallocate after the method call completes. + dp->SetValNeedsCleanup(); + + return true; +} + +nsresult +CallMethodHelper::Invoke() +{ + uint32_t argc = mDispatchParams.Length(); + nsXPTCVariant* argv = mDispatchParams.Elements(); + + return NS_InvokeByIndex(mCallee, mVTableIndex, argc, argv); +} + +/***************************************************************************/ +// interface methods + +JSObject* +XPCWrappedNative::GetJSObject() +{ + return GetFlatJSObject(); +} + +NS_IMETHODIMP XPCWrappedNative::GetNative(nsISupports * *aNative) +{ + // No need to QI here, we already have the correct nsISupports + // vtable. + nsCOMPtr rval = mIdentity; + rval.forget(aNative); + return NS_OK; +} + +NS_IMETHODIMP XPCWrappedNative::GetJSObjectPrototype(JSObject * *aJSObjectPrototype) +{ + *aJSObjectPrototype = HasProto() ? + GetProto()->GetJSProtoObject() : GetFlatJSObject(); + return NS_OK; +} + +nsIPrincipal* +XPCWrappedNative::GetObjectPrincipal() const +{ + nsIPrincipal* principal = GetScope()->GetPrincipal(); +#ifdef DEBUG + // Because of inner window reuse, we can have objects with one principal + // living in a scope with a different (but same-origin) principal. So + // just check same-origin here. + nsCOMPtr objPrin(do_QueryInterface(mIdentity)); + if (objPrin) { + bool equal; + if (!principal) + equal = !objPrin->GetPrincipal(); + else + principal->Equals(objPrin->GetPrincipal(), &equal); + MOZ_ASSERT(equal, "Principal mismatch. Expect bad things to happen"); + } +#endif + return principal; +} + +NS_IMETHODIMP XPCWrappedNative::FindInterfaceWithMember(HandleId name, + nsIInterfaceInfo * *_retval) +{ + RefPtr iface; + XPCNativeMember* member; + + if (GetSet()->FindMember(name, &member, &iface) && iface) { + nsCOMPtr temp = iface->GetInterfaceInfo(); + temp.forget(_retval); + } else + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP XPCWrappedNative::FindInterfaceWithName(HandleId name, + nsIInterfaceInfo * *_retval) +{ + XPCNativeInterface* iface = GetSet()->FindNamedInterface(name); + if (iface) { + nsCOMPtr temp = iface->GetInterfaceInfo(); + temp.forget(_retval); + } else + *_retval = nullptr; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +XPCWrappedNative::HasNativeMember(HandleId name) +{ + XPCNativeMember* member = nullptr; + uint16_t ignored; + return GetSet()->FindMember(name, &member, &ignored) && !!member; +} + +NS_IMETHODIMP XPCWrappedNative::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth-- ; + XPC_LOG_ALWAYS(("XPCWrappedNative @ %x with mRefCnt = %d", this, mRefCnt.get())); + XPC_LOG_INDENT(); + + if (HasProto()) { + XPCWrappedNativeProto* proto = GetProto(); + if (depth && proto) + proto->DebugDump(depth); + else + XPC_LOG_ALWAYS(("mMaybeProto @ %x", proto)); + } else + XPC_LOG_ALWAYS(("Scope @ %x", GetScope())); + + if (depth && mSet) + mSet->DebugDump(depth); + else + XPC_LOG_ALWAYS(("mSet @ %x", mSet.get())); + + XPC_LOG_ALWAYS(("mFlatJSObject of %x", mFlatJSObject.unbarrieredGetPtr())); + XPC_LOG_ALWAYS(("mIdentity of %x", mIdentity.get())); + XPC_LOG_ALWAYS(("mScriptableInfo @ %x", mScriptableInfo)); + + if (depth && mScriptableInfo) { + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("mScriptable @ %x", mScriptableInfo->GetCallback())); + XPC_LOG_ALWAYS(("mFlags of %x", (uint32_t)mScriptableInfo->GetFlags())); + XPC_LOG_ALWAYS(("mJSClass @ %x", mScriptableInfo->GetJSClass())); + XPC_LOG_OUTDENT(); + } + XPC_LOG_OUTDENT(); +#endif + return NS_OK; +} + +/***************************************************************************/ + +char* +XPCWrappedNative::ToString(XPCWrappedNativeTearOff* to /* = nullptr */ ) const +{ +#ifdef DEBUG +# define FMT_ADDR " @ 0x%p" +# define FMT_STR(str) str +# define PARAM_ADDR(w) , w +#else +# define FMT_ADDR "" +# define FMT_STR(str) +# define PARAM_ADDR(w) +#endif + + char* sz = nullptr; + char* name = nullptr; + + XPCNativeScriptableInfo* si = GetScriptableInfo(); + if (si) + name = JS_smprintf("%s", si->GetJSClass()->name); + if (to) { + const char* fmt = name ? " (%s)" : "%s"; + name = JS_sprintf_append(name, fmt, + to->GetInterface()->GetNameString()); + } else if (!name) { + XPCNativeSet* set = GetSet(); + XPCNativeInterface** array = set->GetInterfaceArray(); + RefPtr isupp = XPCNativeInterface::GetISupports(); + uint16_t count = set->GetInterfaceCount(); + + if (count == 1) + name = JS_sprintf_append(name, "%s", array[0]->GetNameString()); + else if (count == 2 && array[0] == isupp) { + name = JS_sprintf_append(name, "%s", array[1]->GetNameString()); + } else { + for (uint16_t i = 0; i < count; i++) { + const char* fmt = (i == 0) ? + "(%s" : (i == count-1) ? + ", %s)" : ", %s"; + name = JS_sprintf_append(name, fmt, + array[i]->GetNameString()); + } + } + } + + if (!name) { + return nullptr; + } + const char* fmt = "[xpconnect wrapped %s" FMT_ADDR FMT_STR(" (native") + FMT_ADDR FMT_STR(")") "]"; + if (si) { + fmt = "[object %s" FMT_ADDR FMT_STR(" (native") FMT_ADDR FMT_STR(")") "]"; + } + sz = JS_smprintf(fmt, name PARAM_ADDR(this) PARAM_ADDR(mIdentity.get())); + + JS_smprintf_free(name); + + + return sz; + +#undef FMT_ADDR +#undef PARAM_ADDR +} + +/***************************************************************************/ + +#ifdef XPC_CHECK_CLASSINFO_CLAIMS +static void DEBUG_CheckClassInfoClaims(XPCWrappedNative* wrapper) +{ + if (!wrapper || !wrapper->GetClassInfo()) + return; + + nsISupports* obj = wrapper->GetIdentityObject(); + XPCNativeSet* set = wrapper->GetSet(); + uint16_t count = set->GetInterfaceCount(); + for (uint16_t i = 0; i < count; i++) { + nsIClassInfo* clsInfo = wrapper->GetClassInfo(); + XPCNativeInterface* iface = set->GetInterfaceAt(i); + nsIInterfaceInfo* info = iface->GetInterfaceInfo(); + const nsIID* iid; + nsISupports* ptr; + + info->GetIIDShared(&iid); + nsresult rv = obj->QueryInterface(*iid, (void**)&ptr); + if (NS_SUCCEEDED(rv)) { + NS_RELEASE(ptr); + continue; + } + if (rv == NS_ERROR_OUT_OF_MEMORY) + continue; + + // Houston, We have a problem... + + char* className = nullptr; + char* contractID = nullptr; + const char* interfaceName; + + info->GetNameShared(&interfaceName); + clsInfo->GetContractID(&contractID); + if (wrapper->GetScriptableInfo()) { + wrapper->GetScriptableInfo()->GetCallback()-> + GetClassName(&className); + } + + + printf("\n!!! Object's nsIClassInfo lies about its interfaces!!!\n" + " classname: %s \n" + " contractid: %s \n" + " unimplemented interface name: %s\n\n", + className ? className : "", + contractID ? contractID : "", + interfaceName); + + if (className) + free(className); + if (contractID) + free(contractID); + } +} +#endif + +NS_IMPL_ISUPPORTS(XPCJSObjectHolder, nsIXPConnectJSObjectHolder) + +JSObject* +XPCJSObjectHolder::GetJSObject() +{ + NS_PRECONDITION(mJSObj, "bad object state"); + return mJSObj; +} + +XPCJSObjectHolder::XPCJSObjectHolder(JSObject* obj) + : mJSObj(obj) +{ + MOZ_ASSERT(obj); + XPCJSContext::Get()->AddObjectHolderRoot(this); +} + +XPCJSObjectHolder::~XPCJSObjectHolder() +{ + RemoveFromRootSet(); +} + +void +XPCJSObjectHolder::TraceJS(JSTracer* trc) +{ + JS::TraceEdge(trc, &mJSObj, "XPCJSObjectHolder::mJSObj"); +} diff --git a/js/xpconnect/src/XPCWrappedNativeInfo.cpp b/js/xpconnect/src/XPCWrappedNativeInfo.cpp new file mode 100644 index 000000000..302454fb5 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeInfo.cpp @@ -0,0 +1,800 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Manage the shared info about interfaces for use by wrappedNatives. */ + +#include "xpcprivate.h" +#include "jswrapper.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/XPTInterfaceInfoManager.h" +#include "nsPrintfCString.h" + +using namespace JS; +using namespace mozilla; + +/***************************************************************************/ + +// XPCNativeMember + +// static +bool +XPCNativeMember::GetCallInfo(JSObject* funobj, + RefPtr* pInterface, + XPCNativeMember** pMember) +{ + funobj = js::UncheckedUnwrap(funobj); + Value memberVal = + js::GetFunctionNativeReserved(funobj, + XPC_FUNCTION_NATIVE_MEMBER_SLOT); + + *pMember = static_cast(memberVal.toPrivate()); + *pInterface = (*pMember)->GetInterface(); + + return true; +} + +bool +XPCNativeMember::NewFunctionObject(XPCCallContext& ccx, + XPCNativeInterface* iface, HandleObject parent, + Value* pval) +{ + MOZ_ASSERT(!IsConstant(), "Only call this if you're sure this is not a constant!"); + + return Resolve(ccx, iface, parent, pval); +} + +bool +XPCNativeMember::Resolve(XPCCallContext& ccx, XPCNativeInterface* iface, + HandleObject parent, Value* vp) +{ + MOZ_ASSERT(iface == GetInterface()); + if (IsConstant()) { + RootedValue resultVal(ccx); + nsXPIDLCString name; + if (NS_FAILED(iface->GetInterfaceInfo()->GetConstant(mIndex, &resultVal, + getter_Copies(name)))) + return false; + + *vp = resultVal; + + return true; + } + // else... + + // This is a method or attribute - we'll be needing a function object + + int argc; + JSNative callback; + + if (IsMethod()) { + const nsXPTMethodInfo* info; + if (NS_FAILED(iface->GetInterfaceInfo()->GetMethodInfo(mIndex, &info))) + return false; + + // Note: ASSUMES that retval is last arg. + argc = (int) info->GetParamCount(); + if (argc && info->GetParam((uint8_t)(argc-1)).IsRetval()) + argc-- ; + + callback = XPC_WN_CallMethod; + } else { + argc = 0; + callback = XPC_WN_GetterSetter; + } + + JSFunction* fun = js::NewFunctionByIdWithReserved(ccx, callback, argc, 0, GetName()); + if (!fun) + return false; + + JSObject* funobj = JS_GetFunctionObject(fun); + if (!funobj) + return false; + + js::SetFunctionNativeReserved(funobj, XPC_FUNCTION_NATIVE_MEMBER_SLOT, + PrivateValue(this)); + js::SetFunctionNativeReserved(funobj, XPC_FUNCTION_PARENT_OBJECT_SLOT, + ObjectValue(*parent)); + + vp->setObject(*funobj); + + return true; +} + +/***************************************************************************/ +// XPCNativeInterface + +XPCNativeInterface::~XPCNativeInterface() +{ + XPCJSContext::Get()->GetIID2NativeInterfaceMap()->Remove(this); +} + +// static +already_AddRefed +XPCNativeInterface::GetNewOrUsed(const nsIID* iid) +{ + RefPtr iface; + XPCJSContext* cx = XPCJSContext::Get(); + + IID2NativeInterfaceMap* map = cx->GetIID2NativeInterfaceMap(); + if (!map) + return nullptr; + + iface = map->Find(*iid); + + if (iface) + return iface.forget(); + + nsCOMPtr info; + XPTInterfaceInfoManager::GetSingleton()->GetInfoForIID(iid, getter_AddRefs(info)); + if (!info) + return nullptr; + + iface = NewInstance(info); + if (!iface) + return nullptr; + + XPCNativeInterface* iface2 = map->Add(iface); + if (!iface2) { + NS_ERROR("failed to add our interface!"); + iface = nullptr; + } else if (iface2 != iface) { + iface = iface2; + } + + return iface.forget(); +} + +// static +already_AddRefed +XPCNativeInterface::GetNewOrUsed(nsIInterfaceInfo* info) +{ + RefPtr iface; + + const nsIID* iid; + if (NS_FAILED(info->GetIIDShared(&iid)) || !iid) + return nullptr; + + XPCJSContext* cx = XPCJSContext::Get(); + + IID2NativeInterfaceMap* map = cx->GetIID2NativeInterfaceMap(); + if (!map) + return nullptr; + + iface = map->Find(*iid); + + if (iface) + return iface.forget(); + + iface = NewInstance(info); + if (!iface) + return nullptr; + + RefPtr iface2 = map->Add(iface); + if (!iface2) { + NS_ERROR("failed to add our interface!"); + iface = nullptr; + } else if (iface2 != iface) { + iface = iface2; + } + + return iface.forget(); +} + +// static +already_AddRefed +XPCNativeInterface::GetNewOrUsed(const char* name) +{ + nsCOMPtr info; + XPTInterfaceInfoManager::GetSingleton()->GetInfoForName(name, getter_AddRefs(info)); + return info ? GetNewOrUsed(info) : nullptr; +} + +// static +already_AddRefed +XPCNativeInterface::GetISupports() +{ + // XXX We should optimize this to cache this common XPCNativeInterface. + return GetNewOrUsed(&NS_GET_IID(nsISupports)); +} + +// static +already_AddRefed +XPCNativeInterface::NewInstance(nsIInterfaceInfo* aInfo) +{ + AutoJSContext cx; + static const uint16_t MAX_LOCAL_MEMBER_COUNT = 16; + XPCNativeMember local_members[MAX_LOCAL_MEMBER_COUNT]; + RefPtr obj; + XPCNativeMember* members = nullptr; + + int i; + bool failed = false; + uint16_t constCount; + uint16_t methodCount; + uint16_t totalCount; + uint16_t realTotalCount = 0; + XPCNativeMember* cur; + RootedString str(cx); + RootedId interfaceName(cx); + + // XXX Investigate lazy init? This is a problem given the + // 'placement new' scheme - we need to at least know how big to make + // the object. We might do a scan of methods to determine needed size, + // then make our object, but avoid init'ing *any* members until asked? + // Find out how often we create these objects w/o really looking at + // (or using) the members. + + bool canScript; + if (NS_FAILED(aInfo->IsScriptable(&canScript)) || !canScript) + return nullptr; + + bool mainProcessScriptableOnly; + if (NS_FAILED(aInfo->IsMainProcessScriptableOnly(&mainProcessScriptableOnly))) + return nullptr; + if (mainProcessScriptableOnly && !XRE_IsParentProcess()) { + nsCOMPtr console(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (console) { + const char* intfNameChars; + aInfo->GetNameShared(&intfNameChars); + nsPrintfCString errorMsg("Use of %s in content process is deprecated.", intfNameChars); + + nsAutoString filename; + uint32_t lineno = 0, column = 0; + nsJSUtils::GetCallingLocation(cx, filename, &lineno, &column); + nsCOMPtr error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(NS_ConvertUTF8toUTF16(errorMsg), + filename, EmptyString(), + lineno, column, nsIScriptError::warningFlag, "chrome javascript"); + console->LogMessage(error); + } + } + + if (NS_FAILED(aInfo->GetMethodCount(&methodCount)) || + NS_FAILED(aInfo->GetConstantCount(&constCount))) + return nullptr; + + // If the interface does not have nsISupports in its inheritance chain + // then we know we can't reflect its methods. However, some interfaces that + // are used just to reflect constants are declared this way. We need to + // go ahead and build the thing. But, we'll ignore whatever methods it may + // have. + if (!nsXPConnect::IsISupportsDescendant(aInfo)) + methodCount = 0; + + totalCount = methodCount + constCount; + + if (totalCount > MAX_LOCAL_MEMBER_COUNT) { + members = new XPCNativeMember[totalCount]; + if (!members) + return nullptr; + } else { + members = local_members; + } + + // NOTE: since getters and setters share a member, we might not use all + // of the member objects. + + for (i = 0; i < methodCount; i++) { + const nsXPTMethodInfo* info; + if (NS_FAILED(aInfo->GetMethodInfo(i, &info))) { + failed = true; + break; + } + + // don't reflect Addref or Release + if (i == 1 || i == 2) + continue; + + if (!XPCConvert::IsMethodReflectable(*info)) + continue; + + str = JS_AtomizeAndPinString(cx, info->GetName()); + if (!str) { + NS_ERROR("bad method name"); + failed = true; + break; + } + jsid name = INTERNED_STRING_TO_JSID(cx, str); + + if (info->IsSetter()) { + MOZ_ASSERT(realTotalCount,"bad setter"); + // Note: ASSUMES Getter/Setter pairs are next to each other + // This is a rule of the typelib spec. + cur = &members[realTotalCount-1]; + MOZ_ASSERT(cur->GetName() == name,"bad setter"); + MOZ_ASSERT(cur->IsReadOnlyAttribute(),"bad setter"); + MOZ_ASSERT(cur->GetIndex() == i-1,"bad setter"); + cur->SetWritableAttribute(); + } else { + // XXX need better way to find dups + // MOZ_ASSERT(!LookupMemberByID(name),"duplicate method name"); + if (realTotalCount == XPCNativeMember::GetMaxIndexInInterface()) { + NS_WARNING("Too many members in interface"); + failed = true; + break; + } + cur = &members[realTotalCount]; + cur->SetName(name); + if (info->IsGetter()) + cur->SetReadOnlyAttribute(i); + else + cur->SetMethod(i); + cur->SetIndexInInterface(realTotalCount); + ++realTotalCount; + } + } + + if (!failed) { + for (i = 0; i < constCount; i++) { + RootedValue constant(cx); + nsXPIDLCString namestr; + if (NS_FAILED(aInfo->GetConstant(i, &constant, getter_Copies(namestr)))) { + failed = true; + break; + } + + str = JS_AtomizeAndPinString(cx, namestr); + if (!str) { + NS_ERROR("bad constant name"); + failed = true; + break; + } + jsid name = INTERNED_STRING_TO_JSID(cx, str); + + // XXX need better way to find dups + //MOZ_ASSERT(!LookupMemberByID(name),"duplicate method/constant name"); + if (realTotalCount == XPCNativeMember::GetMaxIndexInInterface()) { + NS_WARNING("Too many members in interface"); + failed = true; + break; + } + cur = &members[realTotalCount]; + cur->SetName(name); + cur->SetConstant(i); + cur->SetIndexInInterface(realTotalCount); + ++realTotalCount; + } + } + + if (!failed) { + const char* bytes; + if (NS_FAILED(aInfo->GetNameShared(&bytes)) || !bytes || + nullptr == (str = JS_AtomizeAndPinString(cx, bytes))) { + failed = true; + } + interfaceName = INTERNED_STRING_TO_JSID(cx, str); + } + + if (!failed) { + // Use placement new to create an object with the right amount of space + // to hold the members array + int size = sizeof(XPCNativeInterface); + if (realTotalCount > 1) + size += (realTotalCount - 1) * sizeof(XPCNativeMember); + void* place = new char[size]; + if (place) + obj = new(place) XPCNativeInterface(aInfo, interfaceName); + + if (obj) { + obj->mMemberCount = realTotalCount; + // copy valid members + if (realTotalCount) + memcpy(obj->mMembers, members, + realTotalCount * sizeof(XPCNativeMember)); + } + } + + if (members && members != local_members) + delete [] members; + + return obj.forget(); +} + +// static +void +XPCNativeInterface::DestroyInstance(XPCNativeInterface* inst) +{ + inst->~XPCNativeInterface(); + delete [] (char*) inst; +} + +size_t +XPCNativeInterface::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) +{ + return mallocSizeOf(this); +} + +void +XPCNativeInterface::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCNativeInterface @ %x", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("name is %s", GetNameString())); + XPC_LOG_ALWAYS(("mMemberCount is %d", mMemberCount)); + XPC_LOG_ALWAYS(("mInfo @ %x", mInfo.get())); + XPC_LOG_OUTDENT(); +#endif +} + +/***************************************************************************/ +// XPCNativeSetKey + +static PLDHashNumber +HashPointer(const void* ptr) +{ + return NS_PTR_TO_UINT32(ptr) >> 2; +} + +PLDHashNumber +XPCNativeSetKey::Hash() const +{ + PLDHashNumber h = 0; + + if (mBaseSet) { + XPCNativeInterface** current = mBaseSet->GetInterfaceArray(); + uint16_t count = mBaseSet->GetInterfaceCount(); + for (uint16_t i = 0; i < count; i++) { + h ^= HashPointer(*(current++)); + } + } else { + // A newly created set will contain nsISupports first... + RefPtr isupp = XPCNativeInterface::GetISupports(); + h ^= HashPointer(isupp); + + // ...but no more than once. + if (isupp == mAddition) + return h; + } + + if (mAddition) { + h ^= HashPointer(mAddition); + } + + return h; +} + +/***************************************************************************/ +// XPCNativeSet + +XPCNativeSet::~XPCNativeSet() +{ + // Remove |this| before we clear the interfaces to ensure that the + // hashtable look up is correct. + XPCJSContext::Get()->GetNativeSetMap()->Remove(this); + + for (int i = 0; i < mInterfaceCount; i++) { + NS_RELEASE(mInterfaces[i]); + } +} + +// static +already_AddRefed +XPCNativeSet::GetNewOrUsed(const nsIID* iid) +{ + RefPtr iface = + XPCNativeInterface::GetNewOrUsed(iid); + if (!iface) + return nullptr; + + XPCNativeSetKey key(iface); + + XPCJSContext* xpccx = XPCJSContext::Get(); + NativeSetMap* map = xpccx->GetNativeSetMap(); + if (!map) + return nullptr; + + RefPtr set = map->Find(&key); + + if (set) + return set.forget(); + + set = NewInstance({iface.forget()}); + if (!set) + return nullptr; + + if (!map->AddNew(&key, set)) { + NS_ERROR("failed to add our set!"); + set = nullptr; + } + + return set.forget(); +} + +// static +already_AddRefed +XPCNativeSet::GetNewOrUsed(nsIClassInfo* classInfo) +{ + XPCJSContext* xpccx = XPCJSContext::Get(); + ClassInfo2NativeSetMap* map = xpccx->GetClassInfo2NativeSetMap(); + if (!map) + return nullptr; + + RefPtr set = map->Find(classInfo); + + if (set) + return set.forget(); + + nsIID** iidArray = nullptr; + uint32_t iidCount = 0; + + if (NS_FAILED(classInfo->GetInterfaces(&iidCount, &iidArray))) { + // Note: I'm making it OK for this call to fail so that one can add + // nsIClassInfo to classes implemented in script without requiring this + // method to be implemented. + + // Make sure these are set correctly... + iidArray = nullptr; + iidCount = 0; + } + + MOZ_ASSERT((iidCount && iidArray) || !(iidCount || iidArray), "GetInterfaces returned bad array"); + + // !!! from here on we only exit through the 'out' label !!! + + if (iidCount) { + nsTArray> interfaceArray(iidCount); + nsIID** currentIID = iidArray; + + for (uint32_t i = 0; i < iidCount; i++) { + nsIID* iid = *(currentIID++); + if (!iid) { + NS_ERROR("Null found in classinfo interface list"); + continue; + } + + RefPtr iface = + XPCNativeInterface::GetNewOrUsed(iid); + + if (!iface) { + // XXX warn here + continue; + } + + interfaceArray.AppendElement(iface.forget()); + } + + if (interfaceArray.Length() > 0) { + set = NewInstance(Move(interfaceArray)); + if (set) { + NativeSetMap* map2 = xpccx->GetNativeSetMap(); + if (!map2) + goto out; + + XPCNativeSetKey key(set); + + XPCNativeSet* set2 = map2->Add(&key, set); + if (!set2) { + NS_ERROR("failed to add our set!"); + set = nullptr; + goto out; + } + // It is okay to find an existing entry here because + // we did not look for one before we called Add(). + if (set2 != set) { + set = set2; + } + } + } else + set = GetNewOrUsed(&NS_GET_IID(nsISupports)); + } else + set = GetNewOrUsed(&NS_GET_IID(nsISupports)); + + if (set) { +#ifdef DEBUG + XPCNativeSet* set2 = +#endif + map->Add(classInfo, set); + MOZ_ASSERT(set2, "failed to add our set!"); + MOZ_ASSERT(set2 == set, "hashtables inconsistent!"); + } + +out: + if (iidArray) + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(iidCount, iidArray); + + return set.forget(); +} + +// static +void +XPCNativeSet::ClearCacheEntryForClassInfo(nsIClassInfo* classInfo) +{ + XPCJSContext* xpccx = nsXPConnect::GetContextInstance(); + ClassInfo2NativeSetMap* map = xpccx->GetClassInfo2NativeSetMap(); + if (map) + map->Remove(classInfo); +} + +// static +already_AddRefed +XPCNativeSet::GetNewOrUsed(XPCNativeSetKey* key) +{ + NativeSetMap* map = XPCJSContext::Get()->GetNativeSetMap(); + if (!map) + return nullptr; + + RefPtr set = map->Find(key); + + if (set) + return set.forget(); + + if (key->GetBaseSet()) + set = NewInstanceMutate(key); + else + set = NewInstance({key->GetAddition()}); + + if (!set) + return nullptr; + + if (!map->AddNew(key, set)) { + NS_ERROR("failed to add our set!"); + set = nullptr; + } + + return set.forget(); +} + +// static +already_AddRefed +XPCNativeSet::GetNewOrUsed(XPCNativeSet* firstSet, + XPCNativeSet* secondSet, + bool preserveFirstSetOrder) +{ + // Figure out how many interfaces we'll need in the new set. + uint32_t uniqueCount = firstSet->mInterfaceCount; + for (uint32_t i = 0; i < secondSet->mInterfaceCount; ++i) { + if (!firstSet->HasInterface(secondSet->mInterfaces[i])) + uniqueCount++; + } + + // If everything in secondSet was a duplicate, we can just use the first + // set. + if (uniqueCount == firstSet->mInterfaceCount) + return RefPtr(firstSet).forget(); + + // If the secondSet is just a superset of the first, we can use it provided + // that the caller doesn't care about ordering. + if (!preserveFirstSetOrder && uniqueCount == secondSet->mInterfaceCount) + return RefPtr(secondSet).forget(); + + // Ok, darn. Now we have to make a new set. + // + // It would be faster to just create the new set all at once, but that + // would involve wrangling with some pretty hairy code - especially since + // a lot of stuff assumes that sets are created by adding one interface to an + // existing set. So let's just do the slow and easy thing and hope that the + // above optimizations handle the common cases. + RefPtr currentSet = firstSet; + for (uint32_t i = 0; i < secondSet->mInterfaceCount; ++i) { + XPCNativeInterface* iface = secondSet->mInterfaces[i]; + if (!currentSet->HasInterface(iface)) { + // Create a new augmented set, inserting this interface at the end. + XPCNativeSetKey key(currentSet, iface); + currentSet = XPCNativeSet::GetNewOrUsed(&key); + if (!currentSet) + return nullptr; + } + } + + // We've got the union set. Hand it back to the caller. + MOZ_ASSERT(currentSet->mInterfaceCount == uniqueCount); + return currentSet.forget(); +} + +// static +already_AddRefed +XPCNativeSet::NewInstance(nsTArray>&& array) +{ + if (array.Length() == 0) + return nullptr; + + // We impose the invariant: + // "All sets have exactly one nsISupports interface and it comes first." + // This is the place where we impose that rule - even if given inputs + // that don't exactly follow the rule. + + RefPtr isup = XPCNativeInterface::GetISupports(); + uint16_t slots = array.Length() + 1; + + for (auto key = array.begin(); key != array.end(); key++) { + if (*key == isup) + slots--; + } + + // Use placement new to create an object with the right amount of space + // to hold the members array + int size = sizeof(XPCNativeSet); + if (slots > 1) + size += (slots - 1) * sizeof(XPCNativeInterface*); + void* place = new char[size]; + RefPtr obj = new(place) XPCNativeSet(); + + // Stick the nsISupports in front and skip additional nsISupport(s) + XPCNativeInterface** outp = (XPCNativeInterface**) &obj->mInterfaces; + uint16_t memberCount = 1; // for the one member in nsISupports + + NS_ADDREF(*(outp++) = isup); + + for (auto key = array.begin(); key != array.end(); key++) { + RefPtr cur = key->forget(); + if (isup == cur) + continue; + memberCount += cur->GetMemberCount(); + *(outp++) = cur.forget().take(); + } + obj->mMemberCount = memberCount; + obj->mInterfaceCount = slots; + + return obj.forget(); +} + +// static +already_AddRefed +XPCNativeSet::NewInstanceMutate(XPCNativeSetKey* key) +{ + XPCNativeSet* otherSet = key->GetBaseSet(); + XPCNativeInterface* newInterface = key->GetAddition(); + + MOZ_ASSERT(otherSet); + + if (!newInterface) + return nullptr; + + // Use placement new to create an object with the right amount of space + // to hold the members array + int size = sizeof(XPCNativeSet); + size += otherSet->mInterfaceCount * sizeof(XPCNativeInterface*); + void* place = new char[size]; + RefPtr obj = new(place) XPCNativeSet(); + + obj->mMemberCount = otherSet->GetMemberCount() + + newInterface->GetMemberCount(); + obj->mInterfaceCount = otherSet->mInterfaceCount + 1; + + XPCNativeInterface** src = otherSet->mInterfaces; + XPCNativeInterface** dest = obj->mInterfaces; + for (uint16_t i = 0; i < otherSet->mInterfaceCount; i++) { + NS_ADDREF(*dest++ = *src++); + } + NS_ADDREF(*dest++ = newInterface); + + return obj.forget(); +} + +// static +void +XPCNativeSet::DestroyInstance(XPCNativeSet* inst) +{ + inst->~XPCNativeSet(); + delete [] (char*) inst; +} + +size_t +XPCNativeSet::SizeOfIncludingThis(MallocSizeOf mallocSizeOf) +{ + return mallocSizeOf(this); +} + +void +XPCNativeSet::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth--; + XPC_LOG_ALWAYS(("XPCNativeSet @ %x", this)); + XPC_LOG_INDENT(); + + XPC_LOG_ALWAYS(("mInterfaceCount of %d", mInterfaceCount)); + if (depth) { + for (uint16_t i = 0; i < mInterfaceCount; i++) + mInterfaces[i]->DebugDump(depth); + } + XPC_LOG_ALWAYS(("mMemberCount of %d", mMemberCount)); + XPC_LOG_OUTDENT(); +#endif +} diff --git a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp new file mode 100644 index 000000000..12b203b70 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp @@ -0,0 +1,1331 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* JavaScript JSClasses and JSOps for our Wrapped Native JS Objects. */ + +#include "xpcprivate.h" +#include "xpc_make_class.h" +#include "jsprf.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/Preferences.h" +#include "nsIAddonInterposition.h" +#include "AddonWrapper.h" +#include "js/Class.h" + +using namespace mozilla; +using namespace JS; + +/***************************************************************************/ + +// All of the exceptions thrown into JS from this file go through here. +// That makes this a nice place to set a breakpoint. + +static bool Throw(nsresult errNum, JSContext* cx) +{ + XPCThrower::Throw(errNum, cx); + return false; +} + +// Handy macro used in many callback stub below. + +#define THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper) \ + PR_BEGIN_MACRO \ + if (!wrapper) \ + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \ + if (!wrapper->IsValid()) \ + return Throw(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN, cx); \ + PR_END_MACRO + +/***************************************************************************/ + +static bool +ToStringGuts(XPCCallContext& ccx) +{ + char* sz; + XPCWrappedNative* wrapper = ccx.GetWrapper(); + + if (wrapper) + sz = wrapper->ToString(ccx.GetTearOff()); + else + sz = JS_smprintf("[xpconnect wrapped native prototype]"); + + if (!sz) { + JS_ReportOutOfMemory(ccx); + return false; + } + + JSString* str = JS_NewStringCopyZ(ccx, sz); + JS_smprintf_free(sz); + if (!str) + return false; + + ccx.SetRetVal(JS::StringValue(str)); + return true; +} + +/***************************************************************************/ + +static bool +XPC_WN_Shared_ToString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + XPCCallContext ccx(cx, obj); + if (!ccx.IsValid()) + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING)); + ccx.SetArgsAndResultPtr(args.length(), args.array(), vp); + return ToStringGuts(ccx); +} + +static bool +XPC_WN_Shared_ToSource(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + static const char empty[] = "({})"; + JSString* str = JS_NewStringCopyN(cx, empty, sizeof(empty)-1); + if (!str) + return false; + args.rval().setString(str); + + return true; +} + +static bool +XPC_WN_Shared_toPrimitive(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx); + if (!JS_ValueToObject(cx, args.thisv(), &obj)) + return false; + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + JSType hint; + if (!GetFirstArgumentAsTypeHint(cx, args, &hint)) + return false; + + if (hint == JSTYPE_NUMBER) { + args.rval().set(JS_GetNaNValue(cx)); + return true; + } + + MOZ_ASSERT(hint == JSTYPE_STRING || hint == JSTYPE_VOID); + ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING)); + ccx.SetArgsAndResultPtr(0, nullptr, args.rval().address()); + + XPCNativeMember* member = ccx.GetMember(); + if (member && member->IsMethod()) { + if (!XPCWrappedNative::CallMethod(ccx)) + return false; + + if (args.rval().isPrimitive()) + return true; + } + + // else... + return ToStringGuts(ccx); +} + +/***************************************************************************/ + +// A "double wrapped object" is a user JSObject that has been wrapped as a +// wrappedJS in order to be used by native code and then re-wrapped by a +// wrappedNative wrapper to be used by JS code. One might think of it as: +// wrappedNative(wrappedJS(underlying_JSObject)) +// This is done (as opposed to just unwrapping the wrapped JS and automatically +// returning the underlying JSObject) so that JS callers will see what looks +// Like any other xpcom object - and be limited to use its interfaces. +// +// See the comment preceding nsIXPCWrappedJSObjectGetter in nsIXPConnect.idl. + +static JSObject* +GetDoubleWrappedJSObject(XPCCallContext& ccx, XPCWrappedNative* wrapper) +{ + RootedObject obj(ccx); + nsCOMPtr + underware = do_QueryInterface(wrapper->GetIdentityObject()); + if (underware) { + RootedObject mainObj(ccx, underware->GetJSObject()); + if (mainObj) { + RootedId id(ccx, ccx.GetContext()-> + GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT)); + + JSAutoCompartment ac(ccx, mainObj); + + RootedValue val(ccx); + if (JS_GetPropertyById(ccx, mainObj, id, &val) && + !val.isPrimitive()) { + obj = val.toObjectOrNull(); + } + } + } + return obj; +} + +// This is the getter native function we use to handle 'wrappedJSObject' for +// double wrapped JSObjects. + +static bool +XPC_WN_DoubleWrappedGetter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function"); + + RootedObject realObject(cx, GetDoubleWrappedJSObject(ccx, wrapper)); + if (!realObject) { + // This is pretty unexpected at this point. The object originally + // responded to this get property call and now gives no object. + // XXX Should this throw something at the caller? + args.rval().setNull(); + return true; + } + + // It is a double wrapped object. This should really never appear in + // content these days, but addons still do it - see bug 965921. + if (MOZ_UNLIKELY(!nsContentUtils::IsCallerChrome())) { + JS_ReportErrorASCII(cx, "Attempt to use .wrappedJSObject in untrusted code"); + return false; + } + args.rval().setObject(*realObject); + return JS_WrapValue(cx, args.rval()); +} + +/***************************************************************************/ + +// This is our shared function to define properties on our JSObjects. + +/* + * NOTE: + * We *never* set the tearoff names (e.g. nsIFoo) as JS_ENUMERATE. + * We *never* set toString or toSource as JS_ENUMERATE. + */ + +static bool +DefinePropertyIfFound(XPCCallContext& ccx, + HandleObject obj, + HandleId idArg, + XPCNativeSet* set, + XPCNativeInterface* ifaceArg, + XPCNativeMember* member, + XPCWrappedNativeScope* scope, + bool reflectToStringAndToSource, + XPCWrappedNative* wrapperToReflectInterfaceNames, + XPCWrappedNative* wrapperToReflectDoubleWrap, + XPCNativeScriptableInfo* scriptableInfo, + unsigned propFlags, + bool* resolved) +{ + RootedId id(ccx, idArg); + RefPtr iface = ifaceArg; + XPCJSContext* xpccx = ccx.GetContext(); + bool found; + const char* name; + + propFlags |= JSPROP_RESOLVING; + + if (set) { + if (iface) + found = true; + else + found = set->FindMember(id, &member, &iface); + } else + found = (nullptr != (member = iface->FindMember(id))); + + if (!found) { + if (reflectToStringAndToSource) { + JSNative call; + uint32_t flags = 0; + + if (scriptableInfo) { + nsCOMPtr classInfo = do_QueryInterface( + scriptableInfo->GetCallback()); + + if (classInfo) { + nsresult rv = classInfo->GetFlags(&flags); + if (NS_FAILED(rv)) + return Throw(rv, ccx); + } + } + + bool overwriteToString = !(flags & nsIClassInfo::DOM_OBJECT) + || Preferences::GetBool("dom.XPCToStringForDOMClasses", false); + + if(id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING) + && overwriteToString) + { + call = XPC_WN_Shared_ToString; + name = xpccx->GetStringName(XPCJSContext::IDX_TO_STRING); + } else if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE)) { + call = XPC_WN_Shared_ToSource; + name = xpccx->GetStringName(XPCJSContext::IDX_TO_SOURCE); + } else if (id == SYMBOL_TO_JSID( + JS::GetWellKnownSymbol(ccx, JS::SymbolCode::toPrimitive))) + { + call = XPC_WN_Shared_toPrimitive; + name = "[Symbol.toPrimitive]"; + } else { + call = nullptr; + } + + if (call) { + RootedFunction fun(ccx, JS_NewFunction(ccx, call, 0, 0, name)); + if (!fun) { + JS_ReportOutOfMemory(ccx); + return false; + } + + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + RootedObject value(ccx, JS_GetFunctionObject(fun)); + return JS_DefinePropertyById(ccx, obj, id, value, + propFlags & ~JSPROP_ENUMERATE); + } + } + // This *might* be a tearoff name that is not yet part of our + // set. Let's lookup the name and see if it is the name of an + // interface. Then we'll see if the object actually *does* this + // interface and add a tearoff as necessary. + + if (wrapperToReflectInterfaceNames) { + JSAutoByteString name; + RefPtr iface2; + XPCWrappedNativeTearOff* to; + RootedObject jso(ccx); + nsresult rv = NS_OK; + + if (JSID_IS_STRING(id) && + name.encodeLatin1(ccx, JSID_TO_STRING(id)) && + (iface2 = XPCNativeInterface::GetNewOrUsed(name.ptr()), iface2) && + nullptr != (to = wrapperToReflectInterfaceNames-> + FindTearOff(iface2, true, &rv)) && + nullptr != (jso = to->GetJSObject())) + + { + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + return JS_DefinePropertyById(ccx, obj, id, jso, + propFlags & ~JSPROP_ENUMERATE); + } else if (NS_FAILED(rv) && rv != NS_ERROR_NO_INTERFACE) { + return Throw(rv, ccx); + } + } + + // This *might* be a double wrapped JSObject + if (wrapperToReflectDoubleWrap && + id == xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT) && + GetDoubleWrappedJSObject(ccx, wrapperToReflectDoubleWrap)) { + // We build and add a getter function. + // A security check is done on a per-get basis. + + JSFunction* fun; + + id = xpccx->GetStringID(XPCJSContext::IDX_WRAPPED_JSOBJECT); + name = xpccx->GetStringName(XPCJSContext::IDX_WRAPPED_JSOBJECT); + + fun = JS_NewFunction(ccx, XPC_WN_DoubleWrappedGetter, + 0, 0, name); + + if (!fun) + return false; + + RootedObject funobj(ccx, JS_GetFunctionObject(fun)); + if (!funobj) + return false; + + propFlags |= JSPROP_GETTER | JSPROP_SHARED; + propFlags &= ~JSPROP_ENUMERATE; + + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + return JS_DefinePropertyById(ccx, obj, id, UndefinedHandleValue, propFlags, + JS_DATA_TO_FUNC_PTR(JSNative, funobj.get()), + nullptr); + } + + if (resolved) + *resolved = false; + return true; + } + + if (!member) { + if (wrapperToReflectInterfaceNames) { + XPCWrappedNativeTearOff* to = + wrapperToReflectInterfaceNames->FindTearOff(iface, true); + + if (!to) + return false; + RootedObject jso(ccx, to->GetJSObject()); + if (!jso) + return false; + + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + return JS_DefinePropertyById(ccx, obj, id, jso, + propFlags & ~JSPROP_ENUMERATE); + } + if (resolved) + *resolved = false; + return true; + } + + if (member->IsConstant()) { + RootedValue val(ccx); + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + return member->GetConstantValue(ccx, iface, val.address()) && + JS_DefinePropertyById(ccx, obj, id, val, propFlags); + } + + if (scope->HasInterposition()) { + Rooted desc(ccx); + if (!xpc::InterposeProperty(ccx, obj, iface->GetIID(), id, &desc)) + return false; + + if (desc.object()) { + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + desc.attributesRef() |= JSPROP_RESOLVING; + return JS_DefinePropertyById(ccx, obj, id, desc); + } + } + + if (id == xpccx->GetStringID(XPCJSContext::IDX_TO_STRING) || + id == xpccx->GetStringID(XPCJSContext::IDX_TO_SOURCE) || + (scriptableInfo && + scriptableInfo->GetFlags().DontEnumQueryInterface() && + id == xpccx->GetStringID(XPCJSContext::IDX_QUERY_INTERFACE))) + propFlags &= ~JSPROP_ENUMERATE; + + RootedValue funval(ccx); + if (!member->NewFunctionObject(ccx, iface, obj, funval.address())) + return false; + + if (member->IsMethod()) { + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + return JS_DefinePropertyById(ccx, obj, id, funval, propFlags); + } + + // else... + + MOZ_ASSERT(member->IsAttribute(), "way broken!"); + + propFlags |= JSPROP_GETTER | JSPROP_SHARED; + propFlags &= ~JSPROP_READONLY; + JSObject* funobj = funval.toObjectOrNull(); + JSNative getter = JS_DATA_TO_FUNC_PTR(JSNative, funobj); + JSNative setter; + if (member->IsWritableAttribute()) { + propFlags |= JSPROP_SETTER; + setter = JS_DATA_TO_FUNC_PTR(JSNative, funobj); + } else { + setter = nullptr; + } + + AutoResolveName arn(ccx, id); + if (resolved) + *resolved = true; + + return JS_DefinePropertyById(ccx, obj, id, UndefinedHandleValue, propFlags, getter, setter); +} + +/***************************************************************************/ +/***************************************************************************/ + +static bool +XPC_WN_OnlyIWrite_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) +{ + XPCCallContext ccx(cx, obj, nullptr, id); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + // Allow only XPConnect to add/set the property + if (ccx.GetResolveName() == id) + return true; + + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool +XPC_WN_CannotModifyPropertyStub(JSContext* cx, HandleObject obj, HandleId id, + HandleValue v) +{ + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool +XPC_WN_CantDeletePropertyStub(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result) +{ + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool +XPC_WN_CannotModifySetPropertyStub(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleValue vp, ObjectOpResult& result) +{ + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool +XPC_WN_Shared_Enumerate(JSContext* cx, HandleObject obj) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + // Since we aren't going to enumerate tearoff names and the prototype + // handles non-mutated members, we can do this potential short-circuit. + if (!wrapper->HasMutatedSet()) + return true; + + XPCNativeSet* set = wrapper->GetSet(); + XPCNativeSet* protoSet = wrapper->HasProto() ? + wrapper->GetProto()->GetSet() : nullptr; + + uint16_t interface_count = set->GetInterfaceCount(); + XPCNativeInterface** interfaceArray = set->GetInterfaceArray(); + for (uint16_t i = 0; i < interface_count; i++) { + XPCNativeInterface* iface = interfaceArray[i]; + uint16_t member_count = iface->GetMemberCount(); + for (uint16_t k = 0; k < member_count; k++) { + XPCNativeMember* member = iface->GetMemberAt(k); + jsid name = member->GetName(); + + // Skip if this member is going to come from the proto. + uint16_t index; + if (protoSet && + protoSet->FindMember(name, nullptr, &index) && index == i) + continue; + if (!xpc_ForcePropertyResolve(cx, obj, name)) + return false; + } + } + return true; +} + +/***************************************************************************/ + +enum WNHelperType { + WN_NOHELPER, + WN_HELPER +}; + +static void +WrappedNativeFinalize(js::FreeOp* fop, JSObject* obj, WNHelperType helperType) +{ + const js::Class* clazz = js::GetObjectClass(obj); + if (clazz->flags & JSCLASS_DOM_GLOBAL) { + mozilla::dom::DestroyProtoAndIfaceCache(obj); + } + nsISupports* p = static_cast(xpc_GetJSPrivate(obj)); + if (!p) + return; + + XPCWrappedNative* wrapper = static_cast(p); + if (helperType == WN_HELPER) + wrapper->GetScriptableCallback()->Finalize(wrapper, js::CastToJSFreeOp(fop), obj); + wrapper->FlatJSObjectFinalized(); +} + +static void +WrappedNativeObjectMoved(JSObject* obj, const JSObject* old) +{ + nsISupports* p = static_cast(xpc_GetJSPrivate(obj)); + if (!p) + return; + + XPCWrappedNative* wrapper = static_cast(p); + wrapper->FlatJSObjectMoved(obj, old); +} + +void +XPC_WN_NoHelper_Finalize(js::FreeOp* fop, JSObject* obj) +{ + WrappedNativeFinalize(fop, obj, WN_NOHELPER); +} + +/* + * General comment about XPConnect tracing: Given a C++ object |wrapper| and its + * corresponding JS object |obj|, calling |wrapper->TraceSelf| will ask the JS + * engine to mark |obj|. Eventually, this will lead to the trace hook being + * called for |obj|. The trace hook should call |wrapper->TraceInside|, which + * should mark any JS objects held by |wrapper| as members. + */ + +/* static */ void +XPCWrappedNative::Trace(JSTracer* trc, JSObject* obj) +{ + const js::Class* clazz = js::GetObjectClass(obj); + if (clazz->flags & JSCLASS_DOM_GLOBAL) { + mozilla::dom::TraceProtoAndIfaceCache(trc, obj); + } + MOZ_ASSERT(IS_WN_CLASS(clazz)); + + XPCWrappedNative* wrapper = XPCWrappedNative::Get(obj); + if (wrapper && wrapper->IsValid()) + wrapper->TraceInside(trc); +} + +void +XPCWrappedNative_Trace(JSTracer* trc, JSObject* obj) +{ + XPCWrappedNative::Trace(trc, obj); +} + +static bool +XPC_WN_NoHelper_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +{ + XPCCallContext ccx(cx, obj, nullptr, id); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCNativeSet* set = ccx.GetSet(); + if (!set) + return true; + + // Don't resolve properties that are on our prototype. + if (ccx.GetInterface() && !ccx.GetStaticMemberIsLocal()) + return true; + + return DefinePropertyIfFound(ccx, obj, id, + set, nullptr, nullptr, wrapper->GetScope(), + true, wrapper, wrapper, nullptr, + JSPROP_ENUMERATE | + JSPROP_READONLY | + JSPROP_PERMANENT, + resolvedp); +} + +static const js::ClassOps XPC_WN_NoHelper_JSClassOps = { + XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty + XPC_WN_CantDeletePropertyStub, // delProperty + nullptr, // getProperty + nullptr, // setProperty + XPC_WN_Shared_Enumerate, // enumerate + XPC_WN_NoHelper_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_NoHelper_Finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // hasInstance + XPCWrappedNative::Trace, // trace +}; + +const js::ClassExtension XPC_WN_JSClassExtension = { + nullptr, // weakmapKeyDelegateOp + WrappedNativeObjectMoved +}; + +const js::Class XPC_WN_NoHelper_JSClass = { + "XPCWrappedNative_NoHelper", + XPC_WRAPPER_FLAGS | + JSCLASS_IS_WRAPPED_NATIVE | + JSCLASS_PRIVATE_IS_NSISUPPORTS | + JSCLASS_FOREGROUND_FINALIZE, + &XPC_WN_NoHelper_JSClassOps, + JS_NULL_CLASS_SPEC, + &XPC_WN_JSClassExtension, + JS_NULL_OBJECT_OPS +}; + + +/***************************************************************************/ + +bool +XPC_WN_MaybeResolvingPropertyStub(JSContext* cx, HandleObject obj, HandleId id, HandleValue v) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + if (ccx.GetResolvingWrapper() == wrapper) + return true; + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +bool +XPC_WN_MaybeResolvingSetPropertyStub(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleValue vp, ObjectOpResult& result) +{ + result.succeed(); + return XPC_WN_MaybeResolvingPropertyStub(cx, obj, id, vp); +} + +bool +XPC_WN_MaybeResolvingDeletePropertyStub(JSContext* cx, HandleObject obj, HandleId id, + ObjectOpResult& result) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + if (ccx.GetResolvingWrapper() == wrapper) { + return result.succeed(); + } + return Throw(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN, cx); +} + +// macro fun! +#define PRE_HELPER_STUB \ + /* It's very important for "unwrapped" to be rooted here. */ \ + RootedObject unwrapped(cx, js::CheckedUnwrap(obj, false)); \ + if (!unwrapped) { \ + JS_ReportErrorASCII(cx, "Permission denied to operate on object."); \ + return false; \ + } \ + if (!IS_WN_REFLECTOR(unwrapped)) { \ + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); \ + } \ + XPCWrappedNative* wrapper = XPCWrappedNative::Get(unwrapped); \ + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); \ + bool retval = true; \ + nsresult rv = wrapper->GetScriptableCallback()-> + +#define POST_HELPER_STUB \ + if (NS_FAILED(rv)) \ + return Throw(rv, cx); \ + return retval; + +#define POST_HELPER_STUB_WITH_OBJECTOPRESULT(failMethod) \ + if (NS_FAILED(rv)) \ + return Throw(rv, cx); \ + return retval ? result.succeed() : result.failMethod(); + +bool +XPC_WN_Helper_GetProperty(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleValue vp) +{ + PRE_HELPER_STUB + GetProperty(wrapper, cx, obj, id, vp.address(), &retval); + POST_HELPER_STUB +} + +bool +XPC_WN_Helper_SetProperty(JSContext* cx, HandleObject obj, HandleId id, + MutableHandleValue vp, ObjectOpResult& result) +{ + PRE_HELPER_STUB + SetProperty(wrapper, cx, obj, id, vp.address(), &retval); + POST_HELPER_STUB_WITH_OBJECTOPRESULT(failReadOnly) +} + +bool +XPC_WN_Helper_Call(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + // N.B. we want obj to be the callee, not JS_THIS(cx, vp) + RootedObject obj(cx, &args.callee()); + + XPCCallContext ccx(cx, obj, nullptr, JSID_VOIDHANDLE, args.length(), + args.array(), args.rval().address()); + if (!ccx.IsValid()) + return false; + + PRE_HELPER_STUB + Call(wrapper, cx, obj, args, &retval); + POST_HELPER_STUB +} + +bool +XPC_WN_Helper_Construct(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + RootedObject obj(cx, &args.callee()); + if (!obj) + return false; + + XPCCallContext ccx(cx, obj, nullptr, JSID_VOIDHANDLE, args.length(), + args.array(), args.rval().address()); + if (!ccx.IsValid()) + return false; + + PRE_HELPER_STUB + Construct(wrapper, cx, obj, args, &retval); + POST_HELPER_STUB +} + +bool +XPC_WN_Helper_HasInstance(JSContext* cx, HandleObject obj, MutableHandleValue valp, bool* bp) +{ + bool retval2; + PRE_HELPER_STUB + HasInstance(wrapper, cx, obj, valp, &retval2, &retval); + *bp = retval2; + POST_HELPER_STUB +} + +void +XPC_WN_Helper_Finalize(js::FreeOp* fop, JSObject* obj) +{ + WrappedNativeFinalize(fop, obj, WN_HELPER); +} + +bool +XPC_WN_Helper_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +{ + nsresult rv = NS_OK; + bool retval = true; + bool resolved = false; + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + RootedId old(cx, ccx.SetResolveName(id)); + + XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); + if (si && si->GetFlags().WantResolve()) { + XPCWrappedNative* oldResolvingWrapper; + bool allowPropMods = si->GetFlags().AllowPropModsDuringResolve(); + + if (allowPropMods) + oldResolvingWrapper = ccx.SetResolvingWrapper(wrapper); + + rv = si->GetCallback()->Resolve(wrapper, cx, obj, id, &resolved, &retval); + + if (allowPropMods) + (void)ccx.SetResolvingWrapper(oldResolvingWrapper); + } + + old = ccx.SetResolveName(old); + MOZ_ASSERT(old == id, "bad nest"); + + if (NS_FAILED(rv)) { + return Throw(rv, cx); + } + + if (resolved) { + *resolvedp = true; + } else if (wrapper->HasMutatedSet()) { + // We are here if scriptable did not resolve this property and + // it *might* be in the instance set but not the proto set. + + XPCNativeSet* set = wrapper->GetSet(); + XPCNativeSet* protoSet = wrapper->HasProto() ? + wrapper->GetProto()->GetSet() : nullptr; + XPCNativeMember* member = nullptr; + RefPtr iface; + bool IsLocal = false; + + if (set->FindMember(id, &member, &iface, protoSet, &IsLocal) && + IsLocal) { + XPCWrappedNative* oldResolvingWrapper; + + XPCNativeScriptableFlags siFlags(0); + if (si) + siFlags = si->GetFlags(); + + XPCWrappedNative* wrapperForInterfaceNames = + siFlags.DontReflectInterfaceNames() ? nullptr : wrapper; + + oldResolvingWrapper = ccx.SetResolvingWrapper(wrapper); + retval = DefinePropertyIfFound(ccx, obj, id, + set, iface, member, + wrapper->GetScope(), + false, + wrapperForInterfaceNames, + nullptr, si, + JSPROP_ENUMERATE, resolvedp); + (void)ccx.SetResolvingWrapper(oldResolvingWrapper); + } + } + + return retval; +} + +bool +XPC_WN_Helper_Enumerate(JSContext* cx, HandleObject obj) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); + if (!si || !si->GetFlags().WantEnumerate()) + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + + if (!XPC_WN_Shared_Enumerate(cx, obj)) + return false; + + bool retval = true; + nsresult rv = si->GetCallback()->Enumerate(wrapper, cx, obj, &retval); + if (NS_FAILED(rv)) + return Throw(rv, cx); + return retval; +} + +/***************************************************************************/ + +static bool +XPC_WN_JSOp_Enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties, + bool enumerableOnly) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCNativeScriptableInfo* si = wrapper->GetScriptableInfo(); + if (!si || !si->GetFlags().WantNewEnumerate()) + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + + if (!XPC_WN_Shared_Enumerate(cx, obj)) + return false; + + bool retval = true; + nsresult rv = si->GetCallback()->NewEnumerate(wrapper, cx, obj, properties, &retval); + if (NS_FAILED(rv)) + return Throw(rv, cx); + return retval; +} + +/***************************************************************************/ + +// static +XPCNativeScriptableInfo* +XPCNativeScriptableInfo::Construct(const XPCNativeScriptableCreateInfo* sci) +{ + MOZ_ASSERT(sci, "bad param"); + nsCOMPtr callback = sci->GetCallback(); + MOZ_ASSERT(callback); + MOZ_ASSERT(callback->GetScriptableFlags() == sci->GetFlags()); + return new XPCNativeScriptableInfo(callback); +} + +const js::ObjectOps XPC_WN_ObjectOpsWithEnumerate = { + nullptr, // lookupProperty + nullptr, // defineProperty + nullptr, // hasProperty + nullptr, // getProperty + nullptr, // setProperty + nullptr, // getOwnPropertyDescriptor + nullptr, // deleteProperty + nullptr, // watch + nullptr, // unwatch + nullptr, // getElements + XPC_WN_JSOp_Enumerate, + nullptr, // funToString +}; + +/***************************************************************************/ +/***************************************************************************/ + +// Compatibility hack. +// +// XPConnect used to do all sorts of funny tricks to find the "correct" +// |this| object for a given method (often to the detriment of proper +// call/apply). When these tricks were removed, a fair amount of chrome +// code broke, because it was relying on being able to grab methods off +// some XPCOM object (like the nsITelemetry service) and invoke them without +// a proper |this|. So, if it's quite clear that we're in this situation and +// about to use a |this| argument that just won't work, fix things up. +// +// This hack is only useful for getters/setters if someone sets an XPCOM object +// as the prototype for a vanilla JS object and expects the XPCOM attributes to +// work on the derived object, which we really don't want to support. But we +// handle it anyway, for now, to minimize regression risk on an already-risky +// landing. +// +// This hack is mainly useful for the NoHelper JSClass. We also fix up +// Components.utils because it implements nsIXPCScriptable (giving it a custom +// JSClass) but not nsIClassInfo (which would put the methods on a prototype). + +#define IS_NOHELPER_CLASS(clasp) (clasp == &XPC_WN_NoHelper_JSClass) +#define IS_CU_CLASS(clasp) (clasp->name[0] == 'n' && !strcmp(clasp->name, "nsXPCComponents_Utils")) + +MOZ_ALWAYS_INLINE JSObject* +FixUpThisIfBroken(JSObject* obj, JSObject* funobj) +{ + if (funobj) { + JSObject* parentObj = + &js::GetFunctionNativeReserved(funobj, + XPC_FUNCTION_PARENT_OBJECT_SLOT).toObject(); + const js::Class* parentClass = js::GetObjectClass(parentObj); + if (MOZ_UNLIKELY((IS_NOHELPER_CLASS(parentClass) || IS_CU_CLASS(parentClass)) && + (js::GetObjectClass(obj) != parentClass))) + { + return parentObj; + } + } + return obj; +} + +bool +XPC_WN_CallMethod(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function"); + RootedObject funobj(cx, &args.callee()); + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + obj = FixUpThisIfBroken(obj, funobj); + XPCCallContext ccx(cx, obj, funobj, JSID_VOIDHANDLE, args.length(), + args.array(), vp); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + RefPtr iface; + XPCNativeMember* member; + + if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) + return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx); + ccx.SetCallInfo(iface, member, false); + return XPCWrappedNative::CallMethod(ccx); +} + +bool +XPC_WN_GetterSetter(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function"); + RootedObject funobj(cx, &args.callee()); + + RootedObject obj(cx, JS_THIS_OBJECT(cx, vp)); + if (!obj) + return false; + + obj = FixUpThisIfBroken(obj, funobj); + XPCCallContext ccx(cx, obj, funobj, JSID_VOIDHANDLE, args.length(), + args.array(), vp); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + RefPtr iface; + XPCNativeMember* member; + + if (!XPCNativeMember::GetCallInfo(funobj, &iface, &member)) + return Throw(NS_ERROR_XPC_CANT_GET_METHOD_INFO, cx); + + if (args.length() != 0 && member->IsWritableAttribute()) { + ccx.SetCallInfo(iface, member, true); + bool retval = XPCWrappedNative::SetAttribute(ccx); + if (retval) + args.rval().set(args[0]); + return retval; + } + // else... + + ccx.SetCallInfo(iface, member, false); + return XPCWrappedNative::GetAttribute(ccx); +} + +/***************************************************************************/ + +static bool +XPC_WN_Shared_Proto_Enumerate(JSContext* cx, HandleObject obj) +{ + MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_ModsAllowed_Proto_JSClass || + js::GetObjectClass(obj) == &XPC_WN_NoMods_Proto_JSClass, + "bad proto"); + XPCWrappedNativeProto* self = + (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (!self) + return false; + + XPCNativeSet* set = self->GetSet(); + if (!set) + return false; + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) + return false; + + uint16_t interface_count = set->GetInterfaceCount(); + XPCNativeInterface** interfaceArray = set->GetInterfaceArray(); + for (uint16_t i = 0; i < interface_count; i++) { + XPCNativeInterface* iface = interfaceArray[i]; + uint16_t member_count = iface->GetMemberCount(); + + for (uint16_t k = 0; k < member_count; k++) { + if (!xpc_ForcePropertyResolve(cx, obj, iface->GetMemberAt(k)->GetName())) + return false; + } + } + + return true; +} + +static void +XPC_WN_Shared_Proto_Finalize(js::FreeOp* fop, JSObject* obj) +{ + // This can be null if xpc shutdown has already happened + XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (p) + p->JSProtoObjectFinalized(fop, obj); +} + +static void +XPC_WN_Shared_Proto_ObjectMoved(JSObject* obj, const JSObject* old) +{ + // This can be null if xpc shutdown has already happened + XPCWrappedNativeProto* p = (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (p) + p->JSProtoObjectMoved(obj, old); +} + +static void +XPC_WN_Shared_Proto_Trace(JSTracer* trc, JSObject* obj) +{ + // This can be null if xpc shutdown has already happened + XPCWrappedNativeProto* p = + (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (p) + p->TraceInside(trc); +} + +/*****************************************************/ + +static bool +XPC_WN_ModsAllowed_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvep) +{ + MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_ModsAllowed_Proto_JSClass, + "bad proto"); + + XPCWrappedNativeProto* self = + (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (!self) + return false; + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) + return false; + + XPCNativeScriptableInfo* si = self->GetScriptableInfo(); + return DefinePropertyIfFound(ccx, obj, id, + self->GetSet(), nullptr, nullptr, + self->GetScope(), + true, nullptr, nullptr, si, + JSPROP_ENUMERATE, resolvep); +} + +static const js::ClassOps XPC_WN_ModsAllowed_Proto_JSClassOps = { + nullptr, // addProperty + nullptr, // delProperty + nullptr, // getProperty + nullptr, // setProperty + XPC_WN_Shared_Proto_Enumerate, // enumerate + XPC_WN_ModsAllowed_Proto_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_Shared_Proto_Finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // hasInstance + XPC_WN_Shared_Proto_Trace, // trace +}; + +static const js::ClassExtension XPC_WN_Shared_Proto_ClassExtension = { + nullptr, /* weakmapKeyDelegateOp */ + XPC_WN_Shared_Proto_ObjectMoved +}; + +const js::Class XPC_WN_ModsAllowed_Proto_JSClass = { + "XPC_WN_ModsAllowed_Proto_JSClass", + XPC_WRAPPER_FLAGS, + &XPC_WN_ModsAllowed_Proto_JSClassOps, + JS_NULL_CLASS_SPEC, + &XPC_WN_Shared_Proto_ClassExtension, + JS_NULL_OBJECT_OPS +}; + +/***************************************************************************/ + +static bool +XPC_WN_OnlyIWrite_Proto_AddPropertyStub(JSContext* cx, HandleObject obj, HandleId id, + HandleValue v) +{ + MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_NoMods_Proto_JSClass, + "bad proto"); + + XPCWrappedNativeProto* self = + (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (!self) + return false; + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) + return false; + + // Allow XPConnect to add the property only + if (ccx.GetResolveName() == id) + return true; + + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); +} + +static bool +XPC_WN_NoMods_Proto_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +{ + MOZ_ASSERT(js::GetObjectClass(obj) == &XPC_WN_NoMods_Proto_JSClass, + "bad proto"); + + XPCWrappedNativeProto* self = + (XPCWrappedNativeProto*) xpc_GetJSPrivate(obj); + if (!self) + return false; + + XPCCallContext ccx(cx); + if (!ccx.IsValid()) + return false; + + XPCNativeScriptableInfo* si = self->GetScriptableInfo(); + + return DefinePropertyIfFound(ccx, obj, id, + self->GetSet(), nullptr, nullptr, + self->GetScope(), + true, nullptr, nullptr, si, + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_ENUMERATE, resolvedp); +} + +static const js::ClassOps XPC_WN_NoMods_Proto_JSClassOps = { + XPC_WN_OnlyIWrite_Proto_AddPropertyStub, // addProperty + XPC_WN_CantDeletePropertyStub, // delProperty + nullptr, // getProperty + nullptr, // setProperty + XPC_WN_Shared_Proto_Enumerate, // enumerate + XPC_WN_NoMods_Proto_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_Shared_Proto_Finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // hasInstance + XPC_WN_Shared_Proto_Trace, // trace +}; + +const js::Class XPC_WN_NoMods_Proto_JSClass = { + "XPC_WN_NoMods_Proto_JSClass", + XPC_WRAPPER_FLAGS, + &XPC_WN_NoMods_Proto_JSClassOps, + JS_NULL_CLASS_SPEC, + &XPC_WN_Shared_Proto_ClassExtension, + JS_NULL_OBJECT_OPS +}; + +/***************************************************************************/ + +static bool +XPC_WN_TearOff_Enumerate(JSContext* cx, HandleObject obj) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCWrappedNativeTearOff* to = ccx.GetTearOff(); + XPCNativeInterface* iface; + + if (!to || nullptr == (iface = to->GetInterface())) + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + + uint16_t member_count = iface->GetMemberCount(); + for (uint16_t k = 0; k < member_count; k++) { + if (!xpc_ForcePropertyResolve(cx, obj, iface->GetMemberAt(k)->GetName())) + return false; + } + + return true; +} + +static bool +XPC_WN_TearOff_Resolve(JSContext* cx, HandleObject obj, HandleId id, bool* resolvedp) +{ + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wrapper = ccx.GetWrapper(); + THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper); + + XPCWrappedNativeTearOff* to = ccx.GetTearOff(); + XPCNativeInterface* iface; + + if (!to || nullptr == (iface = to->GetInterface())) + return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx); + + return DefinePropertyIfFound(ccx, obj, id, nullptr, iface, nullptr, + wrapper->GetScope(), + true, nullptr, nullptr, nullptr, + JSPROP_READONLY | + JSPROP_PERMANENT | + JSPROP_ENUMERATE, resolvedp); +} + +static void +XPC_WN_TearOff_Finalize(js::FreeOp* fop, JSObject* obj) +{ + XPCWrappedNativeTearOff* p = (XPCWrappedNativeTearOff*) + xpc_GetJSPrivate(obj); + if (!p) + return; + p->JSObjectFinalized(); +} + +static void +XPC_WN_TearOff_ObjectMoved(JSObject* obj, const JSObject* old) +{ + XPCWrappedNativeTearOff* p = (XPCWrappedNativeTearOff*) + xpc_GetJSPrivate(obj); + if (!p) + return; + p->JSObjectMoved(obj, old); +} + +// Make sure XPC_WRAPPER_FLAGS has no reserved slots, so our +// XPC_WN_TEAROFF_RESERVED_SLOTS value is OK. + +static_assert(((XPC_WRAPPER_FLAGS >> JSCLASS_RESERVED_SLOTS_SHIFT) & + JSCLASS_RESERVED_SLOTS_MASK) == 0, + "XPC_WRAPPER_FLAGS should not include any reserved slots"); + +static const js::ClassOps XPC_WN_Tearoff_JSClassOps = { + XPC_WN_OnlyIWrite_AddPropertyStub, // addProperty + XPC_WN_CantDeletePropertyStub, // delProperty + nullptr, // getProperty + nullptr, // setProperty + XPC_WN_TearOff_Enumerate, // enumerate + XPC_WN_TearOff_Resolve, // resolve + nullptr, // mayResolve + XPC_WN_TearOff_Finalize, // finalize + nullptr, // call + nullptr, // construct + nullptr, // hasInstance + nullptr, // trace +}; + +static const js::ClassExtension XPC_WN_Tearoff_JSClassExtension = { + nullptr, // weakmapKeyDelegateOp + XPC_WN_TearOff_ObjectMoved +}; + +const js::Class XPC_WN_Tearoff_JSClass = { + "WrappedNative_TearOff", + XPC_WRAPPER_FLAGS | + JSCLASS_HAS_RESERVED_SLOTS(XPC_WN_TEAROFF_RESERVED_SLOTS), + &XPC_WN_Tearoff_JSClassOps, + JS_NULL_CLASS_SPEC, + &XPC_WN_Tearoff_JSClassExtension +}; diff --git a/js/xpconnect/src/XPCWrappedNativeProto.cpp b/js/xpconnect/src/XPCWrappedNativeProto.cpp new file mode 100644 index 000000000..d9c95a3e6 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeProto.cpp @@ -0,0 +1,208 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Shared proto object for XPCWrappedNative. */ + +#include "xpcprivate.h" +#include "pratom.h" + +using namespace mozilla; + +#ifdef DEBUG +int32_t XPCWrappedNativeProto::gDEBUG_LiveProtoCount = 0; +#endif + +XPCWrappedNativeProto::XPCWrappedNativeProto(XPCWrappedNativeScope* Scope, + nsIClassInfo* ClassInfo, + already_AddRefed&& Set) + : mScope(Scope), + mJSProtoObject(nullptr), + mClassInfo(ClassInfo), + mSet(Set), + mScriptableInfo(nullptr) +{ + // This native object lives as long as its associated JSObject - killed + // by finalization of the JSObject (or explicitly if Init fails). + + MOZ_COUNT_CTOR(XPCWrappedNativeProto); + MOZ_ASSERT(mScope); + +#ifdef DEBUG + gDEBUG_LiveProtoCount++; +#endif +} + +XPCWrappedNativeProto::~XPCWrappedNativeProto() +{ + MOZ_ASSERT(!mJSProtoObject, "JSProtoObject still alive"); + + MOZ_COUNT_DTOR(XPCWrappedNativeProto); + +#ifdef DEBUG + gDEBUG_LiveProtoCount--; +#endif + + // Note that our weak ref to mScope is not to be trusted at this point. + + XPCNativeSet::ClearCacheEntryForClassInfo(mClassInfo); + + delete mScriptableInfo; +} + +bool +XPCWrappedNativeProto::Init(const XPCNativeScriptableCreateInfo* scriptableCreateInfo, + bool callPostCreatePrototype) +{ + AutoJSContext cx; + nsIXPCScriptable* callback = scriptableCreateInfo ? + scriptableCreateInfo->GetCallback() : + nullptr; + if (callback) { + mScriptableInfo = + XPCNativeScriptableInfo::Construct(scriptableCreateInfo); + if (!mScriptableInfo) + return false; + } + + const js::Class* jsclazz = + (mScriptableInfo && + mScriptableInfo->GetFlags().AllowPropModsToPrototype()) + ? &XPC_WN_ModsAllowed_Proto_JSClass + : &XPC_WN_NoMods_Proto_JSClass; + + JS::RootedObject global(cx, mScope->GetGlobalJSObject()); + JS::RootedObject proto(cx, JS_GetObjectPrototype(cx, global)); + mJSProtoObject = JS_NewObjectWithUniqueType(cx, js::Jsvalify(jsclazz), + proto); + + bool success = !!mJSProtoObject; + if (success) { + JS_SetPrivate(mJSProtoObject, this); + if (callPostCreatePrototype) + success = CallPostCreatePrototype(); + } + + return success; +} + +bool +XPCWrappedNativeProto::CallPostCreatePrototype() +{ + AutoJSContext cx; + + // Nothing to do if we don't have a scriptable callback. + nsIXPCScriptable* callback = mScriptableInfo ? mScriptableInfo->GetCallback() + : nullptr; + if (!callback) + return true; + + // Call the helper. This can handle being called if it's not implemented, + // so we don't have to check any sort of "want" here. See xpc_map_end.h. + nsresult rv = callback->PostCreatePrototype(cx, mJSProtoObject); + if (NS_FAILED(rv)) { + JS_SetPrivate(mJSProtoObject, nullptr); + mJSProtoObject = nullptr; + XPCThrower::Throw(rv, cx); + return false; + } + + return true; +} + +void +XPCWrappedNativeProto::JSProtoObjectFinalized(js::FreeOp* fop, JSObject* obj) +{ + MOZ_ASSERT(obj == mJSProtoObject.unbarrieredGet(), "huh?"); + + // Only remove this proto from the map if it is the one in the map. + ClassInfo2WrappedNativeProtoMap* map = GetScope()->GetWrappedNativeProtoMap(); + if (map->Find(mClassInfo) == this) + map->Remove(mClassInfo); + + GetContext()->GetDyingWrappedNativeProtoMap()->Add(this); + + mJSProtoObject.finalize(js::CastToJSFreeOp(fop)->runtime()); +} + +void +XPCWrappedNativeProto::JSProtoObjectMoved(JSObject* obj, const JSObject* old) +{ + MOZ_ASSERT(mJSProtoObject.unbarrieredGet() == old); + mJSProtoObject.init(obj); // Update without triggering barriers. +} + +void +XPCWrappedNativeProto::SystemIsBeingShutDown() +{ + // Note that the instance might receive this call multiple times + // as we walk to here from various places. + + if (mJSProtoObject) { + // short circuit future finalization + JS_SetPrivate(mJSProtoObject, nullptr); + mJSProtoObject = nullptr; + } +} + +// static +XPCWrappedNativeProto* +XPCWrappedNativeProto::GetNewOrUsed(XPCWrappedNativeScope* scope, + nsIClassInfo* classInfo, + const XPCNativeScriptableCreateInfo* scriptableCreateInfo, + bool callPostCreatePrototype) +{ + AutoJSContext cx; + MOZ_ASSERT(scope, "bad param"); + MOZ_ASSERT(classInfo, "bad param"); + + AutoMarkingWrappedNativeProtoPtr proto(cx); + ClassInfo2WrappedNativeProtoMap* map = nullptr; + + map = scope->GetWrappedNativeProtoMap(); + proto = map->Find(classInfo); + if (proto) + return proto; + + RefPtr set = XPCNativeSet::GetNewOrUsed(classInfo); + if (!set) + return nullptr; + + proto = new XPCWrappedNativeProto(scope, classInfo, set.forget()); + + if (!proto || !proto->Init(scriptableCreateInfo, callPostCreatePrototype)) { + delete proto.get(); + return nullptr; + } + + map->Add(classInfo, proto); + + return proto; +} + +void +XPCWrappedNativeProto::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth-- ; + XPC_LOG_ALWAYS(("XPCWrappedNativeProto @ %x", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("gDEBUG_LiveProtoCount is %d", gDEBUG_LiveProtoCount)); + XPC_LOG_ALWAYS(("mScope @ %x", mScope)); + XPC_LOG_ALWAYS(("mJSProtoObject @ %x", mJSProtoObject.get())); + XPC_LOG_ALWAYS(("mSet @ %x", mSet.get())); + XPC_LOG_ALWAYS(("mScriptableInfo @ %x", mScriptableInfo)); + if (depth && mScriptableInfo) { + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("mScriptable @ %x", mScriptableInfo->GetCallback())); + XPC_LOG_ALWAYS(("mFlags of %x", (uint32_t)mScriptableInfo->GetFlags())); + XPC_LOG_ALWAYS(("mJSClass @ %x", mScriptableInfo->GetJSClass())); + XPC_LOG_OUTDENT(); + } + XPC_LOG_OUTDENT(); +#endif +} + + diff --git a/js/xpconnect/src/XPCWrappedNativeScope.cpp b/js/xpconnect/src/XPCWrappedNativeScope.cpp new file mode 100644 index 000000000..24f1067d0 --- /dev/null +++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp @@ -0,0 +1,934 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* Class used to manage the wrapped native objects within a JS scope. */ + +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsPrincipal.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "nsIAddonInterposition.h" +#include "nsIXULRuntime.h" + +#include "mozilla/dom/BindingUtils.h" + +using namespace mozilla; +using namespace xpc; +using namespace JS; + +/***************************************************************************/ + +XPCWrappedNativeScope* XPCWrappedNativeScope::gScopes = nullptr; +XPCWrappedNativeScope* XPCWrappedNativeScope::gDyingScopes = nullptr; +bool XPCWrappedNativeScope::gShutdownObserverInitialized = false; +XPCWrappedNativeScope::InterpositionMap* XPCWrappedNativeScope::gInterpositionMap = nullptr; +InterpositionWhitelistArray* XPCWrappedNativeScope::gInterpositionWhitelists = nullptr; +XPCWrappedNativeScope::AddonSet* XPCWrappedNativeScope::gAllowCPOWAddonSet = nullptr; + +NS_IMPL_ISUPPORTS(XPCWrappedNativeScope::ClearInterpositionsObserver, nsIObserver) + +NS_IMETHODIMP +XPCWrappedNativeScope::ClearInterpositionsObserver::Observe(nsISupports* subject, + const char* topic, + const char16_t* data) +{ + MOZ_ASSERT(strcmp(topic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0); + + // The interposition map holds strong references to interpositions, which + // may themselves be involved in cycles. We need to drop these strong + // references before the cycle collector shuts down. Otherwise we'll + // leak. This observer always runs before CC shutdown. + if (gInterpositionMap) { + delete gInterpositionMap; + gInterpositionMap = nullptr; + } + + if (gInterpositionWhitelists) { + delete gInterpositionWhitelists; + gInterpositionWhitelists = nullptr; + } + + if (gAllowCPOWAddonSet) { + delete gAllowCPOWAddonSet; + gAllowCPOWAddonSet = nullptr; + } + + nsContentUtils::UnregisterShutdownObserver(this); + return NS_OK; +} + +static bool +RemoteXULForbidsXBLScope(nsIPrincipal* aPrincipal, HandleObject aGlobal) +{ + MOZ_ASSERT(aPrincipal); + + // Certain singleton sandoxes are created very early in startup - too early + // to call into AllowXULXBLForPrincipal. We never create XBL scopes for + // sandboxes anway, and certainly not for these singleton scopes. So we just + // short-circuit here. + if (IsSandbox(aGlobal)) + return false; + + // AllowXULXBLForPrincipal will return true for system principal, but we + // don't want that here. + MOZ_ASSERT(nsContentUtils::IsInitialized()); + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) + return false; + + // If this domain isn't whitelisted, we're done. + if (!nsContentUtils::AllowXULXBLForPrincipal(aPrincipal)) + return false; + + // Check the pref to determine how we should behave. + return !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); +} + +XPCWrappedNativeScope::XPCWrappedNativeScope(JSContext* cx, + JS::HandleObject aGlobal) + : mWrappedNativeMap(Native2WrappedNativeMap::newMap(XPC_NATIVE_MAP_LENGTH)), + mWrappedNativeProtoMap(ClassInfo2WrappedNativeProtoMap::newMap(XPC_NATIVE_PROTO_MAP_LENGTH)), + mComponents(nullptr), + mNext(nullptr), + mGlobalJSObject(aGlobal), + mHasCallInterpositions(false), + mIsContentXBLScope(false), + mIsAddonScope(false) +{ + // add ourselves to the scopes list + { + MOZ_ASSERT(aGlobal); + DebugOnly clasp = js::GetObjectClass(aGlobal); + MOZ_ASSERT(clasp->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS | + JSCLASS_HAS_PRIVATE) || + mozilla::dom::IsDOMClass(clasp)); +#ifdef DEBUG + for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) + MOZ_ASSERT(aGlobal != cur->GetGlobalJSObjectPreserveColor(), "dup object"); +#endif + + mNext = gScopes; + gScopes = this; + } + + MOZ_COUNT_CTOR(XPCWrappedNativeScope); + + // Create the compartment private. + JSCompartment* c = js::GetObjectCompartment(aGlobal); + MOZ_ASSERT(!JS_GetCompartmentPrivate(c)); + CompartmentPrivate* priv = new CompartmentPrivate(c); + JS_SetCompartmentPrivate(c, priv); + + // Attach ourselves to the compartment private. + priv->scope = this; + + // Determine whether we would allow an XBL scope in this situation. + // In addition to being pref-controlled, we also disable XBL scopes for + // remote XUL domains, _except_ if we have an additional pref override set. + nsIPrincipal* principal = GetPrincipal(); + mAllowContentXBLScope = !RemoteXULForbidsXBLScope(principal, aGlobal); + + // Determine whether to use an XBL scope. + mUseContentXBLScope = mAllowContentXBLScope; + if (mUseContentXBLScope) { + const js::Class* clasp = js::GetObjectClass(mGlobalJSObject); + mUseContentXBLScope = !strcmp(clasp->name, "Window"); + } + if (mUseContentXBLScope) { + mUseContentXBLScope = principal && !nsContentUtils::IsSystemPrincipal(principal); + } + + JSAddonId* addonId = JS::AddonIdOfObject(aGlobal); + if (gInterpositionMap) { + bool isSystem = nsContentUtils::IsSystemPrincipal(principal); + bool waiveInterposition = priv->waiveInterposition; + InterpositionMap::Ptr interposition = gInterpositionMap->lookup(addonId); + if (!waiveInterposition && interposition) { + MOZ_RELEASE_ASSERT(isSystem); + mInterposition = interposition->value(); + } + // We also want multiprocessCompatible add-ons to have a default interposition. + if (!mInterposition && addonId && isSystem) { + bool interpositionEnabled = mozilla::Preferences::GetBool( + "extensions.interposition.enabled", false); + if (interpositionEnabled) { + mInterposition = do_GetService("@mozilla.org/addons/default-addon-shims;1"); + MOZ_ASSERT(mInterposition); + UpdateInterpositionWhitelist(cx, mInterposition); + } + } + } + + if (addonId) { + // We forbid CPOWs unless they're specifically allowed. + priv->allowCPOWs = gAllowCPOWAddonSet ? gAllowCPOWAddonSet->has(addonId) : false; + } +} + +// static +bool +XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope* scope) +{ + for (XPCWrappedNativeScope* cur = gDyingScopes; cur; cur = cur->mNext) { + if (scope == cur) + return true; + } + return false; +} + +bool +XPCWrappedNativeScope::GetComponentsJSObject(JS::MutableHandleObject obj) +{ + AutoJSContext cx; + if (!mComponents) { + nsIPrincipal* p = GetPrincipal(); + bool system = nsXPConnect::SecurityManager()->IsSystemPrincipal(p); + mComponents = system ? new nsXPCComponents(this) + : new nsXPCComponentsBase(this); + } + + RootedValue val(cx); + xpcObjectHelper helper(mComponents); + bool ok = XPCConvert::NativeInterface2JSObject(&val, nullptr, helper, + nullptr, false, + nullptr); + if (NS_WARN_IF(!ok)) + return false; + + if (NS_WARN_IF(!val.isObject())) + return false; + + // The call to wrap() here is necessary even though the object is same- + // compartment, because it applies our security wrapper. + obj.set(&val.toObject()); + if (NS_WARN_IF(!JS_WrapObject(cx, obj))) + return false; + return true; +} + +void +XPCWrappedNativeScope::ForcePrivilegedComponents() +{ + nsCOMPtr c = do_QueryInterface(mComponents); + if (!c) + mComponents = new nsXPCComponents(this); +} + +bool +XPCWrappedNativeScope::AttachComponentsObject(JSContext* aCx) +{ + RootedObject components(aCx); + if (!GetComponentsJSObject(&components)) + return false; + + RootedObject global(aCx, GetGlobalJSObject()); + MOZ_ASSERT(js::IsObjectInContextCompartment(global, aCx)); + + // The global Components property is non-configurable if it's a full + // nsXPCComponents object. That way, if it's an nsXPCComponentsBase, + // enableUniversalXPConnect can upgrade it later. + unsigned attrs = JSPROP_READONLY | JSPROP_RESOLVING; + nsCOMPtr c = do_QueryInterface(mComponents); + if (c) + attrs |= JSPROP_PERMANENT; + + RootedId id(aCx, XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)); + return JS_DefinePropertyById(aCx, global, id, components, attrs); +} + +static bool +CompartmentPerAddon() +{ + static bool initialized = false; + static bool pref = false; + + if (!initialized) { + pref = Preferences::GetBool("dom.compartment_per_addon", false) || + BrowserTabsRemoteAutostart(); + initialized = true; + } + + return pref; +} + +JSObject* +XPCWrappedNativeScope::EnsureContentXBLScope(JSContext* cx) +{ + JS::RootedObject global(cx, GetGlobalJSObject()); + MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx)); + MOZ_ASSERT(!mIsContentXBLScope); + MOZ_ASSERT(strcmp(js::GetObjectClass(global)->name, + "nsXBLPrototypeScript compilation scope")); + + // If we already have a special XBL scope object, we know what to use. + if (mContentXBLScope) + return mContentXBLScope; + + // If this scope doesn't need an XBL scope, just return the global. + if (!mUseContentXBLScope) + return global; + + // Set up the sandbox options. Note that we use the DOM global as the + // sandboxPrototype so that the XBL scope can access all the DOM objects + // it's accustomed to accessing. + // + // In general wantXrays shouldn't matter much here, but there are weird + // cases when adopting bound content between same-origin globals where a + // in one content XBL scope sees anonymous content in another + // content XBL scope. When that happens, we hit LookupBindingMember for an + // anonymous element that lives in a content XBL scope, which isn't a tested + // or audited codepath. So let's avoid hitting that case by opting out of + // same-origin Xrays. + SandboxOptions options; + options.wantXrays = false; + options.wantComponents = true; + options.proto = global; + options.sameZoneAs = global; + + // Use an nsExpandedPrincipal to create asymmetric security. + nsIPrincipal* principal = GetPrincipal(); + MOZ_ASSERT(!nsContentUtils::IsExpandedPrincipal(principal)); + nsTArray> principalAsArray(1); + principalAsArray.AppendElement(principal); + nsCOMPtr ep = + new nsExpandedPrincipal(principalAsArray, + BasePrincipal::Cast(principal)->OriginAttributesRef()); + + // Create the sandbox. + RootedValue v(cx); + nsresult rv = CreateSandboxObject(cx, &v, ep, options); + NS_ENSURE_SUCCESS(rv, nullptr); + mContentXBLScope = &v.toObject(); + + // Tag it. + CompartmentPrivate::Get(js::UncheckedUnwrap(mContentXBLScope))->scope->mIsContentXBLScope = true; + + // Good to go! + return mContentXBLScope; +} + +bool +XPCWrappedNativeScope::AllowContentXBLScope() +{ + // We only disallow XBL scopes in remote XUL situations. + MOZ_ASSERT_IF(!mAllowContentXBLScope, + nsContentUtils::AllowXULXBLForPrincipal(GetPrincipal())); + return mAllowContentXBLScope; +} + +namespace xpc { +JSObject* +GetXBLScope(JSContext* cx, JSObject* contentScopeArg) +{ + MOZ_ASSERT(!IsInAddonScope(contentScopeArg)); + + JS::RootedObject contentScope(cx, contentScopeArg); + JSAutoCompartment ac(cx, contentScope); + JSObject* scope = CompartmentPrivate::Get(contentScope)->scope->EnsureContentXBLScope(cx); + NS_ENSURE_TRUE(scope, nullptr); // See bug 858642. + scope = js::UncheckedUnwrap(scope); + JS::ExposeObjectToActiveJS(scope); + return scope; +} + +JSObject* +GetScopeForXBLExecution(JSContext* cx, HandleObject contentScope, JSAddonId* addonId) +{ + MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope)); + + RootedObject global(cx, js::GetGlobalForObjectCrossCompartment(contentScope)); + if (IsInContentXBLScope(contentScope)) + return global; + + JSAutoCompartment ac(cx, contentScope); + XPCWrappedNativeScope* nativeScope = CompartmentPrivate::Get(contentScope)->scope; + bool isSystem = nsContentUtils::IsSystemPrincipal(nativeScope->GetPrincipal()); + + RootedObject scope(cx); + if (nativeScope->UseContentXBLScope()) + scope = nativeScope->EnsureContentXBLScope(cx); + else if (addonId && CompartmentPerAddon() && isSystem) + scope = nativeScope->EnsureAddonScope(cx, addonId); + else + scope = global; + + NS_ENSURE_TRUE(scope, nullptr); // See bug 858642. + scope = js::UncheckedUnwrap(scope); + JS::ExposeObjectToActiveJS(scope); + return scope; +} + +bool +AllowContentXBLScope(JSCompartment* c) +{ + XPCWrappedNativeScope* scope = CompartmentPrivate::Get(c)->scope; + return scope && scope->AllowContentXBLScope(); +} + +bool +UseContentXBLScope(JSCompartment* c) +{ + XPCWrappedNativeScope* scope = CompartmentPrivate::Get(c)->scope; + return scope && scope->UseContentXBLScope(); +} + +void +ClearContentXBLScope(JSObject* global) +{ + CompartmentPrivate::Get(global)->scope->ClearContentXBLScope(); +} + +} /* namespace xpc */ + +JSObject* +XPCWrappedNativeScope::EnsureAddonScope(JSContext* cx, JSAddonId* addonId) +{ + JS::RootedObject global(cx, GetGlobalJSObject()); + MOZ_ASSERT(js::IsObjectInContextCompartment(global, cx)); + MOZ_ASSERT(!mIsContentXBLScope); + MOZ_ASSERT(!mIsAddonScope); + MOZ_ASSERT(addonId); + MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(GetPrincipal())); + + // In bug 1092156, we found that add-on scopes don't work correctly when the + // window navigates. The add-on global's prototype is an outer window, so, + // after the navigation, looking up window properties in the add-on scope + // will fail. However, in most cases where the window can be navigated, the + // entire window is part of the add-on. To solve the problem, we avoid + // returning an add-on scope for a window that is already tagged with the + // add-on ID. + if (AddonIdOfObject(global) == addonId) + return global; + + // If we already have an addon scope object, we know what to use. + for (size_t i = 0; i < mAddonScopes.Length(); i++) { + if (JS::AddonIdOfObject(js::UncheckedUnwrap(mAddonScopes[i])) == addonId) + return mAddonScopes[i]; + } + + SandboxOptions options; + options.wantComponents = true; + options.proto = global; + options.sameZoneAs = global; + options.addonId = JS::StringOfAddonId(addonId); + options.writeToGlobalPrototype = true; + + RootedValue v(cx); + nsresult rv = CreateSandboxObject(cx, &v, GetPrincipal(), options); + NS_ENSURE_SUCCESS(rv, nullptr); + mAddonScopes.AppendElement(&v.toObject()); + + CompartmentPrivate::Get(js::UncheckedUnwrap(&v.toObject()))->scope->mIsAddonScope = true; + return &v.toObject(); +} + +JSObject* +xpc::GetAddonScope(JSContext* cx, JS::HandleObject contentScope, JSAddonId* addonId) +{ + MOZ_RELEASE_ASSERT(!IsInAddonScope(contentScope)); + + if (!addonId || !CompartmentPerAddon()) { + return js::GetGlobalForObjectCrossCompartment(contentScope); + } + + JSAutoCompartment ac(cx, contentScope); + XPCWrappedNativeScope* nativeScope = CompartmentPrivate::Get(contentScope)->scope; + if (nativeScope->GetPrincipal() != nsXPConnect::SystemPrincipal()) { + // This can happen if, for example, Jetpack loads an unprivileged HTML + // page from the add-on. It's not clear what to do there, so we just use + // the normal global. + return js::GetGlobalForObjectCrossCompartment(contentScope); + } + JSObject* scope = nativeScope->EnsureAddonScope(cx, addonId); + NS_ENSURE_TRUE(scope, nullptr); + + scope = js::UncheckedUnwrap(scope); + JS::ExposeObjectToActiveJS(scope); + return scope; +} + +XPCWrappedNativeScope::~XPCWrappedNativeScope() +{ + MOZ_COUNT_DTOR(XPCWrappedNativeScope); + + // We can do additional cleanup assertions here... + + MOZ_ASSERT(0 == mWrappedNativeMap->Count(), "scope has non-empty map"); + delete mWrappedNativeMap; + + MOZ_ASSERT(0 == mWrappedNativeProtoMap->Count(), "scope has non-empty map"); + delete mWrappedNativeProtoMap; + + // This should not be necessary, since the Components object should die + // with the scope but just in case. + if (mComponents) + mComponents->mScope = nullptr; + + // XXX we should assert that we are dead or that xpconnect has shutdown + // XXX might not want to do this at xpconnect shutdown time??? + mComponents = nullptr; + + if (mXrayExpandos.initialized()) + mXrayExpandos.destroy(); + + JSContext* cx = dom::danger::GetJSContext(); + mContentXBLScope.finalize(cx); + for (size_t i = 0; i < mAddonScopes.Length(); i++) + mAddonScopes[i].finalize(cx); + mGlobalJSObject.finalize(cx); +} + +// static +void +XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(JSTracer* trc, XPCJSContext* cx) +{ + // Do JS::TraceEdge for all wrapped natives with external references, as + // well as any DOM expando objects. + for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) { + for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + XPCWrappedNative* wrapper = entry->value; + if (wrapper->HasExternalReference() && !wrapper->IsWrapperExpired()) + wrapper->TraceSelf(trc); + } + + if (cur->mDOMExpandoSet) { + for (DOMExpandoSet::Enum e(*cur->mDOMExpandoSet); !e.empty(); e.popFront()) + JS::TraceEdge(trc, &e.mutableFront(), "DOM expando object"); + } + } +} + +static void +SuspectDOMExpandos(JSObject* obj, nsCycleCollectionNoteRootCallback& cb) +{ + MOZ_ASSERT(dom::GetDOMClass(obj) && dom::GetDOMClass(obj)->mDOMObjectIsISupports); + nsISupports* native = dom::UnwrapDOMObject(obj); + cb.NoteXPCOMRoot(native); +} + +// static +void +XPCWrappedNativeScope::SuspectAllWrappers(XPCJSContext* cx, + nsCycleCollectionNoteRootCallback& cb) +{ + for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) { + for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { + static_cast(i.Get())->value->Suspect(cb); + } + + if (cur->mDOMExpandoSet) { + for (DOMExpandoSet::Range r = cur->mDOMExpandoSet->all(); !r.empty(); r.popFront()) + SuspectDOMExpandos(r.front().unbarrieredGet(), cb); + } + } +} + +// static +void +XPCWrappedNativeScope::UpdateWeakPointersAfterGC(XPCJSContext* cx) +{ + // If this is called from the finalization callback in JSGC_MARK_END then + // JSGC_FINALIZE_END must always follow it calling + // FinishedFinalizationPhaseOfGC and clearing gDyingScopes in + // KillDyingScopes. + MOZ_ASSERT(!gDyingScopes, "JSGC_MARK_END without JSGC_FINALIZE_END"); + + XPCWrappedNativeScope* prev = nullptr; + XPCWrappedNativeScope* cur = gScopes; + + while (cur) { + // Sweep waivers. + if (cur->mWaiverWrapperMap) + cur->mWaiverWrapperMap->Sweep(); + + XPCWrappedNativeScope* next = cur->mNext; + + if (cur->mContentXBLScope) + cur->mContentXBLScope.updateWeakPointerAfterGC(); + for (size_t i = 0; i < cur->mAddonScopes.Length(); i++) + cur->mAddonScopes[i].updateWeakPointerAfterGC(); + + // Check for finalization of the global object or update our pointer if + // it was moved. + if (cur->mGlobalJSObject) { + cur->mGlobalJSObject.updateWeakPointerAfterGC(); + if (!cur->mGlobalJSObject) { + // Move this scope from the live list to the dying list. + if (prev) + prev->mNext = next; + else + gScopes = next; + cur->mNext = gDyingScopes; + gDyingScopes = cur; + cur = nullptr; + } + } + + if (cur) + prev = cur; + cur = next; + } +} + +// static +void +XPCWrappedNativeScope::SweepAllWrappedNativeTearOffs() +{ + for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) { + for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + entry->value->SweepTearOffs(); + } + } +} + +// static +void +XPCWrappedNativeScope::KillDyingScopes() +{ + XPCWrappedNativeScope* cur = gDyingScopes; + while (cur) { + XPCWrappedNativeScope* next = cur->mNext; + if (cur->mGlobalJSObject) + CompartmentPrivate::Get(cur->mGlobalJSObject)->scope = nullptr; + delete cur; + cur = next; + } + gDyingScopes = nullptr; +} + +//static +void +XPCWrappedNativeScope::SystemIsBeingShutDown() +{ + int liveScopeCount = 0; + + XPCWrappedNativeScope* cur; + + // First move all the scopes to the dying list. + + cur = gScopes; + while (cur) { + XPCWrappedNativeScope* next = cur->mNext; + cur->mNext = gDyingScopes; + gDyingScopes = cur; + cur = next; + liveScopeCount++; + } + gScopes = nullptr; + + // We're forcibly killing scopes, rather than allowing them to go away + // when they're ready. As such, we need to do some cleanup before they + // can safely be destroyed. + + for (cur = gDyingScopes; cur; cur = cur->mNext) { + // Give the Components object a chance to try to clean up. + if (cur->mComponents) + cur->mComponents->SystemIsBeingShutDown(); + + // Walk the protos first. Wrapper shutdown can leave dangling + // proto pointers in the proto map. + for (auto i = cur->mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + entry->value->SystemIsBeingShutDown(); + i.Remove(); + } + for (auto i = cur->mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + XPCWrappedNative* wrapper = entry->value; + if (wrapper->IsValid()) { + wrapper->SystemIsBeingShutDown(); + } + i.Remove(); + } + } + + // Now it is safe to kill all the scopes. + KillDyingScopes(); +} + + +/***************************************************************************/ + +JSObject* +XPCWrappedNativeScope::GetExpandoChain(HandleObject target) +{ + MOZ_ASSERT(ObjectScope(target) == this); + if (!mXrayExpandos.initialized()) + return nullptr; + return mXrayExpandos.lookup(target); +} + +bool +XPCWrappedNativeScope::SetExpandoChain(JSContext* cx, HandleObject target, + HandleObject chain) +{ + MOZ_ASSERT(ObjectScope(target) == this); + MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); + MOZ_ASSERT_IF(chain, ObjectScope(chain) == this); + if (!mXrayExpandos.initialized() && !mXrayExpandos.init(cx)) + return false; + return mXrayExpandos.put(cx, target, chain); +} + +/* static */ bool +XPCWrappedNativeScope::SetAddonInterposition(JSContext* cx, + JSAddonId* addonId, + nsIAddonInterposition* interp) +{ + if (!gInterpositionMap) { + gInterpositionMap = new InterpositionMap(); + bool ok = gInterpositionMap->init(); + NS_ENSURE_TRUE(ok, false); + + if (!gShutdownObserverInitialized) { + gShutdownObserverInitialized = true; + nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver()); + } + } + if (interp) { + bool ok = gInterpositionMap->put(addonId, interp); + NS_ENSURE_TRUE(ok, false); + UpdateInterpositionWhitelist(cx, interp); + } else { + gInterpositionMap->remove(addonId); + } + return true; +} + +/* static */ bool +XPCWrappedNativeScope::AllowCPOWsInAddon(JSContext* cx, + JSAddonId* addonId, + bool allow) +{ + if (!gAllowCPOWAddonSet) { + gAllowCPOWAddonSet = new AddonSet(); + bool ok = gAllowCPOWAddonSet->init(); + NS_ENSURE_TRUE(ok, false); + + if (!gShutdownObserverInitialized) { + gShutdownObserverInitialized = true; + nsContentUtils::RegisterShutdownObserver(new ClearInterpositionsObserver()); + } + } + if (allow) { + bool ok = gAllowCPOWAddonSet->put(addonId); + NS_ENSURE_TRUE(ok, false); + } else { + gAllowCPOWAddonSet->remove(addonId); + } + return true; +} + +nsCOMPtr +XPCWrappedNativeScope::GetInterposition() +{ + return mInterposition; +} + +/* static */ InterpositionWhitelist* +XPCWrappedNativeScope::GetInterpositionWhitelist(nsIAddonInterposition* interposition) +{ + if (!gInterpositionWhitelists) + return nullptr; + + InterpositionWhitelistArray& wls = *gInterpositionWhitelists; + for (size_t i = 0; i < wls.Length(); i++) { + if (wls[i].interposition == interposition) + return &wls[i].whitelist; + } + + return nullptr; +} + +/* static */ bool +XPCWrappedNativeScope::UpdateInterpositionWhitelist(JSContext* cx, + nsIAddonInterposition* interposition) +{ + // We want to set the interpostion whitelist only once. + InterpositionWhitelist* whitelist = GetInterpositionWhitelist(interposition); + if (whitelist) + return true; + + // The hashsets in gInterpositionWhitelists do not have a copy constructor so + // a reallocation for the array will lead to a memory corruption. If you + // need more interpositions, change the capacity of the array please. + static const size_t MAX_INTERPOSITION = 8; + if (!gInterpositionWhitelists) + gInterpositionWhitelists = new InterpositionWhitelistArray(MAX_INTERPOSITION); + + MOZ_RELEASE_ASSERT(MAX_INTERPOSITION > gInterpositionWhitelists->Length() + 1); + InterpositionWhitelistPair* newPair = gInterpositionWhitelists->AppendElement(); + newPair->interposition = interposition; + if (!newPair->whitelist.init()) { + JS_ReportOutOfMemory(cx); + return false; + } + + whitelist = &newPair->whitelist; + + RootedValue whitelistVal(cx); + nsresult rv = interposition->GetWhitelist(&whitelistVal); + if (NS_FAILED(rv)) { + JS_ReportErrorASCII(cx, "Could not get the whitelist from the interposition."); + return false; + } + + if (!whitelistVal.isObject()) { + JS_ReportErrorASCII(cx, "Whitelist must be an array."); + return false; + } + + // We want to enter the whitelist's compartment to avoid any wrappers. + // To be on the safe side let's make sure that it's a system compartment + // and we don't accidentally trigger some content function here by parsing + // the whitelist object. + RootedObject whitelistObj(cx, &whitelistVal.toObject()); + whitelistObj = js::UncheckedUnwrap(whitelistObj); + if (!AccessCheck::isChrome(whitelistObj)) { + JS_ReportErrorASCII(cx, "Whitelist must be from system scope."); + return false; + } + + { + JSAutoCompartment ac(cx, whitelistObj); + + bool isArray; + if (!JS_IsArrayObject(cx, whitelistObj, &isArray)) + return false; + + if (!isArray) { + JS_ReportErrorASCII(cx, "Whitelist must be an array."); + return false; + } + + uint32_t length; + if (!JS_GetArrayLength(cx, whitelistObj, &length)) + return false; + + for (uint32_t i = 0; i < length; i++) { + RootedValue idval(cx); + if (!JS_GetElement(cx, whitelistObj, i, &idval)) + return false; + + if (!idval.isString()) { + JS_ReportErrorASCII(cx, "Whitelist must contain strings only."); + return false; + } + + RootedString str(cx, idval.toString()); + str = JS_AtomizeAndPinJSString(cx, str); + if (!str) { + JS_ReportErrorASCII(cx, "String internization failed."); + return false; + } + + // By internizing the id's we ensure that they won't get + // GCed so we can use them as hash keys. + jsid id = INTERNED_STRING_TO_JSID(cx, str); + if (!whitelist->put(JSID_BITS(id))) { + JS_ReportOutOfMemory(cx); + return false; + } + } + } + + return true; +} + +/***************************************************************************/ + +// static +void +XPCWrappedNativeScope::DebugDumpAllScopes(int16_t depth) +{ +#ifdef DEBUG + depth-- ; + + // get scope count. + int count = 0; + XPCWrappedNativeScope* cur; + for (cur = gScopes; cur; cur = cur->mNext) + count++ ; + + XPC_LOG_ALWAYS(("chain of %d XPCWrappedNativeScope(s)", count)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("gDyingScopes @ %x", gDyingScopes)); + if (depth) + for (cur = gScopes; cur; cur = cur->mNext) + cur->DebugDump(depth); + XPC_LOG_OUTDENT(); +#endif +} + +void +XPCWrappedNativeScope::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth-- ; + XPC_LOG_ALWAYS(("XPCWrappedNativeScope @ %x", this)); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("mNext @ %x", mNext)); + XPC_LOG_ALWAYS(("mComponents @ %x", mComponents.get())); + XPC_LOG_ALWAYS(("mGlobalJSObject @ %x", mGlobalJSObject.get())); + + XPC_LOG_ALWAYS(("mWrappedNativeMap @ %x with %d wrappers(s)", + mWrappedNativeMap, mWrappedNativeMap->Count())); + // iterate contexts... + if (depth && mWrappedNativeMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mWrappedNativeMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + entry->value->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + + XPC_LOG_ALWAYS(("mWrappedNativeProtoMap @ %x with %d protos(s)", + mWrappedNativeProtoMap, + mWrappedNativeProtoMap->Count())); + // iterate contexts... + if (depth && mWrappedNativeProtoMap->Count()) { + XPC_LOG_INDENT(); + for (auto i = mWrappedNativeProtoMap->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + entry->value->DebugDump(depth); + } + XPC_LOG_OUTDENT(); + } + XPC_LOG_OUTDENT(); +#endif +} + +void +XPCWrappedNativeScope::AddSizeOfAllScopesIncludingThis(ScopeSizeInfo* scopeSizeInfo) +{ + for (XPCWrappedNativeScope* cur = gScopes; cur; cur = cur->mNext) + cur->AddSizeOfIncludingThis(scopeSizeInfo); +} + +void +XPCWrappedNativeScope::AddSizeOfIncludingThis(ScopeSizeInfo* scopeSizeInfo) +{ + scopeSizeInfo->mScopeAndMapSize += scopeSizeInfo->mMallocSizeOf(this); + scopeSizeInfo->mScopeAndMapSize += + mWrappedNativeMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); + scopeSizeInfo->mScopeAndMapSize += + mWrappedNativeProtoMap->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); + + if (dom::HasProtoAndIfaceCache(mGlobalJSObject)) { + dom::ProtoAndIfaceCache* cache = dom::GetProtoAndIfaceCache(mGlobalJSObject); + scopeSizeInfo->mProtoAndIfaceCacheSize += + cache->SizeOfIncludingThis(scopeSizeInfo->mMallocSizeOf); + } + + // There are other XPCWrappedNativeScope members that could be measured; + // the above ones have been seen by DMD to be worth measuring. More stuff + // may be added later. +} diff --git a/js/xpconnect/src/XPCWrapper.cpp b/js/xpconnect/src/XPCWrapper.cpp new file mode 100644 index 000000000..a6b331017 --- /dev/null +++ b/js/xpconnect/src/XPCWrapper.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "WrapperFactory.h" +#include "AccessCheck.h" + +using namespace xpc; +using namespace mozilla; +using namespace JS; + +namespace XPCNativeWrapper { + +static inline +bool +ThrowException(nsresult ex, JSContext* cx) +{ + XPCThrower::Throw(ex, cx); + + return false; +} + +static bool +UnwrapNW(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + if (args.length() != 1) { + return ThrowException(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx); + } + + JS::RootedValue v(cx, args[0]); + if (!v.isObject() || !js::IsCrossCompartmentWrapper(&v.toObject()) || + !WrapperFactory::AllowWaiver(&v.toObject())) { + args.rval().set(v); + return true; + } + + bool ok = xpc::WrapperFactory::WaiveXrayAndWrap(cx, &v); + NS_ENSURE_TRUE(ok, false); + args.rval().set(v); + return true; +} + +static bool +XrayWrapperConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + JS::CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() == 0) { + return ThrowException(NS_ERROR_XPC_NOT_ENOUGH_ARGS, cx); + } + + if (!args[0].isObject()) { + args.rval().set(args[0]); + return true; + } + + args.rval().setObject(*js::UncheckedUnwrap(&args[0].toObject())); + return JS_WrapValue(cx, args.rval()); +} +// static +bool +AttachNewConstructorObject(JSContext* aCx, JS::HandleObject aGlobalObject) +{ + // Pushing a JSContext calls ActivateDebugger which calls this function, so + // we can't use an AutoJSContext here until JSD is gone. + JSAutoCompartment ac(aCx, aGlobalObject); + JSFunction* xpcnativewrapper = + JS_DefineFunction(aCx, aGlobalObject, "XPCNativeWrapper", + XrayWrapperConstructor, 1, + JSPROP_READONLY | JSPROP_PERMANENT | JSFUN_STUB_GSOPS | JSFUN_CONSTRUCTOR); + if (!xpcnativewrapper) { + return false; + } + JS::RootedObject obj(aCx, JS_GetFunctionObject(xpcnativewrapper)); + return JS_DefineFunction(aCx, obj, "unwrap", UnwrapNW, 1, + JSPROP_READONLY | JSPROP_PERMANENT) != nullptr; +} + +} // namespace XPCNativeWrapper + +namespace XPCWrapper { + +JSObject* +UnsafeUnwrapSecurityWrapper(JSObject* obj) +{ + if (js::IsProxy(obj)) { + return js::UncheckedUnwrap(obj); + } + + return obj; +} + +} // namespace XPCWrapper diff --git a/js/xpconnect/src/XPCWrapper.h b/js/xpconnect/src/XPCWrapper.h new file mode 100644 index 000000000..7e0ed8c1f --- /dev/null +++ b/js/xpconnect/src/XPCWrapper.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XPC_WRAPPER_H +#define XPC_WRAPPER_H 1 + +#include "js/TypeDecls.h" + +namespace XPCNativeWrapper { + +// Given an XPCWrappedNative pointer and the name of the function on +// XPCNativeScriptableFlags corresponding with a flag, returns 'true' +// if the flag is set. +// XXX Convert to using GetFlags() and not a macro. +#define NATIVE_HAS_FLAG(_wn, _flag) \ + ((_wn)->GetScriptableInfo() && \ + (_wn)->GetScriptableInfo()->GetFlags()._flag()) + +bool +AttachNewConstructorObject(JSContext* aCx, JS::HandleObject aGlobalObject); + +} // namespace XPCNativeWrapper + +// This namespace wraps some common functionality between the three existing +// wrappers. Its main purpose is to allow XPCCrossOriginWrapper to act both +// as an XPCSafeJSObjectWrapper and as an XPCNativeWrapper when required to +// do so (the decision is based on the principals of the wrapper and wrapped +// objects). +namespace XPCWrapper { + +JSObject* +UnsafeUnwrapSecurityWrapper(JSObject* obj); + +} // namespace XPCWrapper + + +#endif diff --git a/js/xpconnect/src/jsshell.msg b/js/xpconnect/src/jsshell.msg new file mode 100644 index 000000000..078d75e28 --- /dev/null +++ b/js/xpconnect/src/jsshell.msg @@ -0,0 +1,12 @@ +/* -*- 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/. */ + +/* + Error messages for JSShell. See js.msg for format. +*/ + +MSG_DEF(JSSMSG_NOT_AN_ERROR, 0, 0, JSEXN_ERR, "") +MSG_DEF(JSSMSG_CANT_OPEN, 1, 2, JSEXN_ERR, "can't open {0}: {1}") diff --git a/js/xpconnect/src/moz.build b/js/xpconnect/src/moz.build new file mode 100644 index 000000000..7e787bb56 --- /dev/null +++ b/js/xpconnect/src/moz.build @@ -0,0 +1,70 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'BackstagePass.h', + 'qsObjectHelper.h', + 'XPCJSMemoryReporter.h', + 'xpcObjectHelper.h', + 'xpcpublic.h', +] + +UNIFIED_SOURCES += [ + 'ExportHelpers.cpp', + 'nsScriptError.cpp', + 'nsScriptErrorWithStack.cpp', + 'nsXPConnect.cpp', + 'Sandbox.cpp', + 'XPCCallContext.cpp', + 'XPCConvert.cpp', + 'XPCDebug.cpp', + 'XPCException.cpp', + 'XPCJSContext.cpp', + 'XPCJSID.cpp', + 'XPCJSWeakReference.cpp', + 'XPCLocale.cpp', + 'XPCLog.cpp', + 'XPCMaps.cpp', + 'XPCModule.cpp', + 'XPCRuntimeService.cpp', + 'XPCShellImpl.cpp', + 'XPCString.cpp', + 'XPCThrower.cpp', + 'XPCVariant.cpp', + 'XPCWrappedJS.cpp', + 'XPCWrappedJSClass.cpp', + 'XPCWrappedNative.cpp', + 'XPCWrappedNativeInfo.cpp', + 'XPCWrappedNativeJSOps.cpp', + 'XPCWrappedNativeProto.cpp', + 'XPCWrappedNativeScope.cpp', + 'XPCWrapper.cpp', +] + +# XPCComponents.cpp cannot be built in unified mode because it uses plarena.h. +SOURCES += [ + 'XPCComponents.cpp', +] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../loader', + '../wrappers', + '/caps', + '/dom/base', + '/dom/html', + '/dom/svg', + '/dom/workers', + '/layout/base', + '/layout/style', + '/xpcom/reflect/xptinfo', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-shadow', '-Werror=format'] diff --git a/js/xpconnect/src/nsScriptError.cpp b/js/xpconnect/src/nsScriptError.cpp new file mode 100644 index 000000000..ff687bc44 --- /dev/null +++ b/js/xpconnect/src/nsScriptError.cpp @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsIScriptError implementation. Defined here, lacking a JS-specific + * place to put XPCOM things. + */ + +#include "xpcprivate.h" +#include "jsprf.h" +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "nsGlobalWindow.h" +#include "nsPIDOMWindow.h" +#include "nsILoadContext.h" +#include "nsIDocShell.h" +#include "nsIScriptError.h" +#include "nsISensitiveInfoHiddenURI.h" + +static_assert(nsIScriptError::errorFlag == JSREPORT_ERROR && + nsIScriptError::warningFlag == JSREPORT_WARNING && + nsIScriptError::exceptionFlag == JSREPORT_EXCEPTION && + nsIScriptError::strictFlag == JSREPORT_STRICT && + nsIScriptError::infoFlag == JSREPORT_USER_1, + "flags should be consistent"); + +nsScriptErrorBase::nsScriptErrorBase() + : mMessage(), + mMessageName(), + mSourceName(), + mLineNumber(0), + mSourceLine(), + mColumnNumber(0), + mFlags(0), + mCategory(), + mOuterWindowID(0), + mInnerWindowID(0), + mTimeStamp(0), + mInitializedOnMainThread(false), + mIsFromPrivateWindow(false) +{ +} + +nsScriptErrorBase::~nsScriptErrorBase() {} + +void +nsScriptErrorBase::InitializeOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mInitializedOnMainThread); + + if (mInnerWindowID) { + nsGlobalWindow* window = + nsGlobalWindow::GetInnerWindowWithId(mInnerWindowID); + if (window) { + nsPIDOMWindowOuter* outer = window->GetOuterWindow(); + if (outer) + mOuterWindowID = outer->WindowID(); + + nsIDocShell* docShell = window->GetDocShell(); + nsCOMPtr loadContext = do_QueryInterface(docShell); + + if (loadContext) { + // Never mark exceptions from chrome windows as having come from + // private windows, since we always want them to be reported. + nsIPrincipal* winPrincipal = window->GetPrincipal(); + mIsFromPrivateWindow = loadContext->UsePrivateBrowsing() && + !nsContentUtils::IsSystemPrincipal(winPrincipal); + } + } + } + + mInitializedOnMainThread = true; +} + +// nsIConsoleMessage methods +NS_IMETHODIMP +nsScriptErrorBase::GetMessageMoz(char16_t** result) { + nsresult rv; + + nsAutoCString message; + rv = ToString(message); + if (NS_FAILED(rv)) + return rv; + + *result = UTF8ToNewUnicode(message); + if (!*result) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + + +NS_IMETHODIMP +nsScriptErrorBase::GetLogLevel(uint32_t* aLogLevel) +{ + if (mFlags & (uint32_t)nsIScriptError::infoFlag) { + *aLogLevel = nsIConsoleMessage::info; + } else if (mFlags & (uint32_t)nsIScriptError::warningFlag) { + *aLogLevel = nsIConsoleMessage::warn; + } else { + *aLogLevel = nsIConsoleMessage::error; + } + return NS_OK; +} + +// nsIScriptError methods +NS_IMETHODIMP +nsScriptErrorBase::GetErrorMessage(nsAString& aResult) { + aResult.Assign(mMessage); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetSourceName(nsAString& aResult) { + aResult.Assign(mSourceName); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetSourceLine(nsAString& aResult) { + aResult.Assign(mSourceLine); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetLineNumber(uint32_t* result) { + *result = mLineNumber; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetColumnNumber(uint32_t* result) { + *result = mColumnNumber; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetFlags(uint32_t* result) { + *result = mFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetCategory(char** result) { + *result = ToNewCString(mCategory); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetStack(JS::MutableHandleValue aStack) { + aStack.setUndefined(); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::SetStack(JS::HandleValue aStack) { + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetErrorMessageName(nsAString& aErrorMessageName) { + aErrorMessageName = mMessageName; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::SetErrorMessageName(const nsAString& aErrorMessageName) { + mMessageName = aErrorMessageName; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::Init(const nsAString& message, + const nsAString& sourceName, + const nsAString& sourceLine, + uint32_t lineNumber, + uint32_t columnNumber, + uint32_t flags, + const char* category) +{ + return InitWithWindowID(message, sourceName, sourceLine, lineNumber, + columnNumber, flags, + category ? nsDependentCString(category) + : EmptyCString(), + 0); +} + +NS_IMETHODIMP +nsScriptErrorBase::InitWithWindowID(const nsAString& message, + const nsAString& sourceName, + const nsAString& sourceLine, + uint32_t lineNumber, + uint32_t columnNumber, + uint32_t flags, + const nsACString& category, + uint64_t aInnerWindowID) +{ + mMessage.Assign(message); + + if (!sourceName.IsEmpty()) { + mSourceName.Assign(sourceName); + + nsCOMPtr uri; + nsAutoCString pass; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), sourceName)) && + NS_SUCCEEDED(uri->GetPassword(pass)) && + !pass.IsEmpty()) { + nsCOMPtr safeUri = + do_QueryInterface(uri); + + nsAutoCString loc; + if (safeUri && + NS_SUCCEEDED(safeUri->GetSensitiveInfoHiddenSpec(loc))) { + mSourceName.Assign(NS_ConvertUTF8toUTF16(loc)); + } + } + } + + mLineNumber = lineNumber; + mSourceLine.Assign(sourceLine); + mColumnNumber = columnNumber; + mFlags = flags; + mCategory = category; + mTimeStamp = JS_Now() / 1000; + mInnerWindowID = aInnerWindowID; + + if (aInnerWindowID && NS_IsMainThread()) { + InitializeOnMainThread(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::ToString(nsACString& /*UTF8*/ aResult) +{ + static const char format0[] = + "[%s: \"%s\" {file: \"%s\" line: %d column: %d source: \"%s\"}]"; + static const char format1[] = + "[%s: \"%s\" {file: \"%s\" line: %d}]"; + static const char format2[] = + "[%s: \"%s\"]"; + + static const char error[] = "JavaScript Error"; + static const char warning[] = "JavaScript Warning"; + + const char* severity = !(mFlags & JSREPORT_WARNING) ? error : warning; + + char* temp; + char* tempMessage = nullptr; + char* tempSourceName = nullptr; + char* tempSourceLine = nullptr; + + if (!mMessage.IsEmpty()) + tempMessage = ToNewUTF8String(mMessage); + if (!mSourceName.IsEmpty()) + // Use at most 512 characters from mSourceName. + tempSourceName = ToNewUTF8String(StringHead(mSourceName, 512)); + if (!mSourceLine.IsEmpty()) + // Use at most 512 characters from mSourceLine. + tempSourceLine = ToNewUTF8String(StringHead(mSourceLine, 512)); + + if (nullptr != tempSourceName && nullptr != tempSourceLine) + temp = JS_smprintf(format0, + severity, + tempMessage, + tempSourceName, + mLineNumber, + mColumnNumber, + tempSourceLine); + else if (!mSourceName.IsEmpty()) + temp = JS_smprintf(format1, + severity, + tempMessage, + tempSourceName, + mLineNumber); + else + temp = JS_smprintf(format2, + severity, + tempMessage); + + if (nullptr != tempMessage) + free(tempMessage); + if (nullptr != tempSourceName) + free(tempSourceName); + if (nullptr != tempSourceLine) + free(tempSourceLine); + + if (!temp) + return NS_ERROR_OUT_OF_MEMORY; + + aResult.Assign(temp); + JS_smprintf_free(temp); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetOuterWindowID(uint64_t* aOuterWindowID) +{ + NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread, + "This can't be safely determined off the main thread, " + "returning an inaccurate value!"); + + if (!mInitializedOnMainThread && NS_IsMainThread()) { + InitializeOnMainThread(); + } + + *aOuterWindowID = mOuterWindowID; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetInnerWindowID(uint64_t* aInnerWindowID) +{ + *aInnerWindowID = mInnerWindowID; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetTimeStamp(int64_t* aTimeStamp) +{ + *aTimeStamp = mTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorBase::GetIsFromPrivateWindow(bool* aIsFromPrivateWindow) +{ + NS_WARNING_ASSERTION(NS_IsMainThread() || mInitializedOnMainThread, + "This can't be safely determined off the main thread, " + "returning an inaccurate value!"); + + if (!mInitializedOnMainThread && NS_IsMainThread()) { + InitializeOnMainThread(); + } + + *aIsFromPrivateWindow = mIsFromPrivateWindow; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsScriptError, nsIConsoleMessage, nsIScriptError) diff --git a/js/xpconnect/src/nsScriptErrorWithStack.cpp b/js/xpconnect/src/nsScriptErrorWithStack.cpp new file mode 100644 index 000000000..edc12fa76 --- /dev/null +++ b/js/xpconnect/src/nsScriptErrorWithStack.cpp @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * nsScriptErrorWithStack implementation. + * a main-thread-only, cycle-collected subclass of nsScriptErrorBase + * that can store a SavedFrame stack trace object. + */ + +#include "xpcprivate.h" +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "nsGlobalWindow.h" +#include "nsCycleCollectionParticipant.h" + + +namespace { + +static nsCString +FormatStackString(JSContext* cx, HandleObject aStack) { + JS::RootedString formattedStack(cx); + + if (!JS::BuildStackString(cx, aStack, &formattedStack)) { + return nsCString(); + } + + nsAutoJSString stackJSString; + if (!stackJSString.init(cx, formattedStack)) { + return nsCString(); + } + + return NS_ConvertUTF16toUTF8(stackJSString.get()); +} + +} + + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsScriptErrorWithStack) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsScriptErrorWithStack) + tmp->mStack = nullptr; +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsScriptErrorWithStack) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsScriptErrorWithStack) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mStack) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsScriptErrorWithStack) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsScriptErrorWithStack) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsScriptErrorWithStack) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIConsoleMessage) + NS_INTERFACE_MAP_ENTRY(nsIScriptError) +NS_INTERFACE_MAP_END + +nsScriptErrorWithStack::nsScriptErrorWithStack(JS::HandleObject aStack) + : mStack(aStack) +{ + MOZ_ASSERT(NS_IsMainThread(), "You can't use this class on workers."); + mozilla::HoldJSObjects(this); +} + +nsScriptErrorWithStack::~nsScriptErrorWithStack() { + mozilla::DropJSObjects(this); +} + +NS_IMETHODIMP +nsScriptErrorWithStack::Init(const nsAString& message, + const nsAString& sourceName, + const nsAString& sourceLine, + uint32_t lineNumber, + uint32_t columnNumber, + uint32_t flags, + const char* category) +{ + MOZ_CRASH("nsScriptErrorWithStack requires to be initialized with a document, by using InitWithWindowID"); +} + +NS_IMETHODIMP +nsScriptErrorWithStack::GetStack(JS::MutableHandleValue aStack) { + aStack.setObjectOrNull(mStack); + return NS_OK; +} + +NS_IMETHODIMP +nsScriptErrorWithStack::ToString(nsACString& /*UTF8*/ aResult) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCString message; + nsresult rv = nsScriptErrorBase::ToString(message); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mStack) { + aResult.Assign(message); + return NS_OK; + } + + AutoJSAPI jsapi; + if (!jsapi.Init(mStack)) { + return NS_ERROR_FAILURE; + } + + JSContext* cx = jsapi.cx(); + RootedObject stack(cx, mStack); + nsCString stackString = FormatStackString(cx, stack); + nsCString combined = message + NS_LITERAL_CSTRING("\n") + stackString; + aResult.Assign(combined); + + return NS_OK; +} diff --git a/js/xpconnect/src/nsXPConnect.cpp b/js/xpconnect/src/nsXPConnect.cpp new file mode 100644 index 000000000..0466175b1 --- /dev/null +++ b/js/xpconnect/src/nsXPConnect.cpp @@ -0,0 +1,1336 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* High level class and public functions implementation. */ + +#include "mozilla/Assertions.h" +#include "mozilla/Base64.h" +#include "mozilla/Likely.h" +#include "mozilla/Unused.h" + +#include "xpcprivate.h" +#include "XPCWrapper.h" +#include "jsfriendapi.h" +#include "nsJSEnvironment.h" +#include "nsThreadUtils.h" +#include "nsDOMJSUtils.h" + +#include "WrapperFactory.h" +#include "AccessCheck.h" + +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/Promise.h" + +#include "nsDOMMutationObserver.h" +#include "nsICycleCollectorListener.h" +#include "mozilla/XPTInterfaceInfoManager.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsScriptSecurityManager.h" +#include "nsIPermissionManager.h" +#include "nsContentUtils.h" +#include "jsfriendapi.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace xpc; +using namespace JS; + +NS_IMPL_ISUPPORTS(nsXPConnect, nsIXPConnect) + +nsXPConnect* nsXPConnect::gSelf = nullptr; +bool nsXPConnect::gOnceAliveNowDead = false; + +// Global cache of the default script security manager (QI'd to +// nsIScriptSecurityManager) and the system principal. +nsIScriptSecurityManager* nsXPConnect::gScriptSecurityManager = nullptr; +nsIPrincipal* nsXPConnect::gSystemPrincipal = nullptr; + +const char XPC_CONTEXT_STACK_CONTRACTID[] = "@mozilla.org/js/xpc/ContextStack;1"; +const char XPC_EXCEPTION_CONTRACTID[] = "@mozilla.org/js/xpc/Exception;1"; +const char XPC_CONSOLE_CONTRACTID[] = "@mozilla.org/consoleservice;1"; +const char XPC_SCRIPT_ERROR_CONTRACTID[] = "@mozilla.org/scripterror;1"; +const char XPC_ID_CONTRACTID[] = "@mozilla.org/js/xpc/ID;1"; +const char XPC_XPCONNECT_CONTRACTID[] = "@mozilla.org/js/xpc/XPConnect;1"; + +/***************************************************************************/ + +nsXPConnect::nsXPConnect() + : mContext(nullptr), + mShuttingDown(false) +{ + mContext = XPCJSContext::newXPCJSContext(); + if (!mContext) { + NS_RUNTIMEABORT("Couldn't create XPCJSContext."); + } +} + +nsXPConnect::~nsXPConnect() +{ + mContext->DeleteSingletonScopes(); + + // In order to clean up everything properly, we need to GC twice: once now, + // to clean anything that can go away on its own (like the Junk Scope, which + // we unrooted above), and once after forcing a bunch of shutdown in + // XPConnect, to clean the stuff we forcibly disconnected. The forced + // shutdown code defaults to leaking in a number of situations, so we can't + // get by with only the second GC. :-( + mContext->GarbageCollect(JS::gcreason::XPCONNECT_SHUTDOWN); + + mShuttingDown = true; + XPCWrappedNativeScope::SystemIsBeingShutDown(); + + // The above causes us to clean up a bunch of XPConnect data structures, + // after which point we need to GC to clean everything up. We need to do + // this before deleting the XPCJSContext, because doing so destroys the + // maps that our finalize callback depends on. + mContext->GarbageCollect(JS::gcreason::XPCONNECT_SHUTDOWN); + + NS_RELEASE(gSystemPrincipal); + gScriptSecurityManager = nullptr; + + // shutdown the logging system + XPC_LOG_FINISH(); + + delete mContext; + + gSelf = nullptr; + gOnceAliveNowDead = true; +} + +// static +void +nsXPConnect::InitStatics() +{ + gSelf = new nsXPConnect(); + gOnceAliveNowDead = false; + if (!gSelf->mContext) { + NS_RUNTIMEABORT("Couldn't create XPCJSContext."); + } + + // Initial extra ref to keep the singleton alive + // balanced by explicit call to ReleaseXPConnectSingleton() + NS_ADDREF(gSelf); + + // Fire up the SSM. + nsScriptSecurityManager::InitStatics(); + gScriptSecurityManager = nsScriptSecurityManager::GetScriptSecurityManager(); + gScriptSecurityManager->GetSystemPrincipal(&gSystemPrincipal); + MOZ_RELEASE_ASSERT(gSystemPrincipal); + + if (!JS::InitSelfHostedCode(gSelf->mContext->Context())) + MOZ_CRASH("InitSelfHostedCode failed"); + if (!gSelf->mContext->JSContextInitialized(gSelf->mContext->Context())) + MOZ_CRASH("JSContextInitialized failed"); + + // Initialize our singleton scopes. + gSelf->mContext->InitSingletonScopes(); +} + +nsXPConnect* +nsXPConnect::GetSingleton() +{ + nsXPConnect* xpc = nsXPConnect::XPConnect(); + NS_IF_ADDREF(xpc); + return xpc; +} + +// static +void +nsXPConnect::ReleaseXPConnectSingleton() +{ + nsXPConnect* xpc = gSelf; + if (xpc) { + nsrefcnt cnt; + NS_RELEASE2(xpc, cnt); + } +} + +// static +XPCJSContext* +nsXPConnect::GetContextInstance() +{ + nsXPConnect* xpc = XPConnect(); + return xpc->GetContext(); +} + +// static +bool +nsXPConnect::IsISupportsDescendant(nsIInterfaceInfo* info) +{ + bool found = false; + if (info) + info->HasAncestor(&NS_GET_IID(nsISupports), &found); + return found; +} + +void +xpc::ErrorReport::Init(JSErrorReport* aReport, const char* aToStringResult, + bool aIsChrome, uint64_t aWindowID) +{ + mCategory = aIsChrome ? NS_LITERAL_CSTRING("chrome javascript") + : NS_LITERAL_CSTRING("content javascript"); + mWindowID = aWindowID; + + ErrorReportToMessageString(aReport, mErrorMsg); + if (mErrorMsg.IsEmpty() && aToStringResult) { + AppendUTF8toUTF16(aToStringResult, mErrorMsg); + } + + if (!aReport->filename) { + mFileName.SetIsVoid(true); + } else { + mFileName.AssignWithConversion(aReport->filename); + } + + mSourceLine.Assign(aReport->linebuf(), aReport->linebufLength()); + const JSErrorFormatString* efs = js::GetErrorMessage(nullptr, aReport->errorNumber); + + if (efs == nullptr) { + mErrorMsgName.AssignASCII(""); + } else { + mErrorMsgName.AssignASCII(efs->name); + } + + mLineNumber = aReport->lineno; + mColumn = aReport->column; + mFlags = aReport->flags; + mIsMuted = aReport->isMuted; +} + +void +xpc::ErrorReport::Init(JSContext* aCx, mozilla::dom::Exception* aException, + bool aIsChrome, uint64_t aWindowID) +{ + mCategory = aIsChrome ? NS_LITERAL_CSTRING("chrome javascript") + : NS_LITERAL_CSTRING("content javascript"); + mWindowID = aWindowID; + + aException->GetErrorMessage(mErrorMsg); + + aException->GetFilename(aCx, mFileName); + if (mFileName.IsEmpty()) { + mFileName.SetIsVoid(true); + } + aException->GetLineNumber(aCx, &mLineNumber); + aException->GetColumnNumber(&mColumn); + + mFlags = JSREPORT_EXCEPTION; +} + +static LazyLogModule gJSDiagnostics("JSDiagnostics"); + +void +xpc::ErrorReport::LogToConsole() +{ + LogToConsoleWithStack(nullptr); +} +void +xpc::ErrorReport::LogToConsoleWithStack(JS::HandleObject aStack) +{ + // Log to stdout. + if (nsContentUtils::DOMWindowDumpEnabled()) { + nsAutoCString error; + error.AssignLiteral("JavaScript "); + if (JSREPORT_IS_STRICT(mFlags)) + error.AppendLiteral("strict "); + if (JSREPORT_IS_WARNING(mFlags)) + error.AppendLiteral("warning: "); + else + error.AppendLiteral("error: "); + error.Append(NS_LossyConvertUTF16toASCII(mFileName)); + error.AppendLiteral(", line "); + error.AppendInt(mLineNumber, 10); + error.AppendLiteral(": "); + error.Append(NS_LossyConvertUTF16toASCII(mErrorMsg)); + + fprintf(stderr, "%s\n", error.get()); + fflush(stderr); + } + + MOZ_LOG(gJSDiagnostics, + JSREPORT_IS_WARNING(mFlags) ? LogLevel::Warning : LogLevel::Error, + ("file %s, line %u\n%s", NS_LossyConvertUTF16toASCII(mFileName).get(), + mLineNumber, NS_LossyConvertUTF16toASCII(mErrorMsg).get())); + + // Log to the console. We do this last so that we can simply return if + // there's no console service without affecting the other reporting + // mechanisms. + nsCOMPtr consoleService = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + + nsCOMPtr errorObject; + if (mWindowID && aStack) { + // Only set stack on messages related to a document + // As we cache messages in the console service, + // we have to ensure not leaking them after the related + // context is destroyed and we only track document lifecycle for now. + errorObject = new nsScriptErrorWithStack(aStack); + } else { + errorObject = new nsScriptError(); + } + errorObject->SetErrorMessageName(mErrorMsgName); + NS_ENSURE_TRUE_VOID(consoleService); + + nsresult rv = errorObject->InitWithWindowID(mErrorMsg, mFileName, mSourceLine, + mLineNumber, mColumn, mFlags, + mCategory, mWindowID); + NS_ENSURE_SUCCESS_VOID(rv); + consoleService->LogMessage(errorObject); + +} + +/* static */ +void +xpc::ErrorReport::ErrorReportToMessageString(JSErrorReport* aReport, + nsAString& aString) +{ + aString.Truncate(); + if (aReport->message()) { + JSFlatString* name = js::GetErrorTypeName(CycleCollectedJSContext::Get()->Context(), aReport->exnType); + if (name) { + AssignJSFlatString(aString, name); + aString.AppendLiteral(": "); + } + aString.Append(NS_ConvertUTF8toUTF16(aReport->message().c_str())); + } +} + +/***************************************************************************/ + + +nsresult +nsXPConnect::GetInfoForIID(const nsIID * aIID, nsIInterfaceInfo** info) +{ + return XPTInterfaceInfoManager::GetSingleton()->GetInfoForIID(aIID, info); +} + +nsresult +nsXPConnect::GetInfoForName(const char * name, nsIInterfaceInfo** info) +{ + nsresult rv = XPTInterfaceInfoManager::GetSingleton()->GetInfoForName(name, info); + return NS_FAILED(rv) ? NS_OK : NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +nsXPConnect::GarbageCollect(uint32_t reason) +{ + GetContext()->GarbageCollect(reason); + return NS_OK; +} + +void +xpc_MarkInCCGeneration(nsISupports* aVariant, uint32_t aGeneration) +{ + nsCOMPtr variant = do_QueryInterface(aVariant); + if (variant) { + variant->SetCCGeneration(aGeneration); + variant->GetJSVal(); // Unmarks gray JSObject. + XPCVariant* weak = variant.get(); + variant = nullptr; + if (weak->IsPurple()) { + weak->RemovePurple(); + } + } +} + +void +xpc_TryUnmarkWrappedGrayObject(nsISupports* aWrappedJS) +{ + // QIing to nsIXPConnectWrappedJSUnmarkGray may have side effects! + nsCOMPtr wjsug = + do_QueryInterface(aWrappedJS); + Unused << wjsug; + MOZ_ASSERT(!wjsug, "One should never be able to QI to " + "nsIXPConnectWrappedJSUnmarkGray successfully!"); +} + +/***************************************************************************/ +/***************************************************************************/ +// nsIXPConnect interface methods... + +template +static inline T UnexpectedFailure(T rv) +{ + NS_ERROR("This is not supposed to fail!"); + return rv; +} + +void +xpc::TraceXPCGlobal(JSTracer* trc, JSObject* obj) +{ + if (js::GetObjectClass(obj)->flags & JSCLASS_DOM_GLOBAL) + mozilla::dom::TraceProtoAndIfaceCache(trc, obj); + + // We might be called from a GC during the creation of a global, before we've + // been able to set up the compartment private or the XPCWrappedNativeScope, + // so we need to null-check those. + xpc::CompartmentPrivate* compartmentPrivate = xpc::CompartmentPrivate::Get(obj); + if (compartmentPrivate && compartmentPrivate->scope) + compartmentPrivate->scope->TraceInside(trc); +} + + +namespace xpc { + +JSObject* +CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal, + JS::CompartmentOptions& aOptions) +{ + MOZ_ASSERT(NS_IsMainThread(), "using a principal off the main thread?"); + MOZ_ASSERT(principal); + + MOZ_RELEASE_ASSERT(principal != nsContentUtils::GetNullSubjectPrincipal(), + "The null subject principal is getting inherited - fix that!"); + + RootedObject global(cx, + JS_NewGlobalObject(cx, clasp, nsJSPrincipals::get(principal), + JS::DontFireOnNewGlobalHook, aOptions)); + if (!global) + return nullptr; + JSAutoCompartment ac(cx, global); + + // The constructor automatically attaches the scope to the compartment private + // of |global|. + (void) new XPCWrappedNativeScope(cx, global); + + if (clasp->flags & JSCLASS_DOM_GLOBAL) { +#ifdef DEBUG + // Verify that the right trace hook is called. Note that this doesn't + // work right for wrapped globals, since the tracing situation there is + // more complicated. Manual inspection shows that they do the right + // thing. Also note that we only check this for JSCLASS_DOM_GLOBAL + // classes because xpc::TraceXPCGlobal won't call + // TraceProtoAndIfaceCache unless that flag is set. + if (!((const js::Class*)clasp)->isWrappedNative()) + { + VerifyTraceProtoAndIfaceCacheCalledTracer trc(cx); + TraceChildren(&trc, GCCellPtr(global.get())); + MOZ_ASSERT(trc.ok, "Trace hook on global needs to call TraceXPCGlobal for XPConnect compartments."); + } +#endif + + const char* className = clasp->name; + AllocateProtoAndIfaceCache(global, + (strcmp(className, "Window") == 0 || + strcmp(className, "ChromeWindow") == 0) + ? ProtoAndIfaceCache::WindowLike + : ProtoAndIfaceCache::NonWindowLike); + } + + return global; +} + +void +InitGlobalObjectOptions(JS::CompartmentOptions& aOptions, + nsIPrincipal* aPrincipal) +{ + bool shouldDiscardSystemSource = ShouldDiscardSystemSource(); + bool extraWarningsForSystemJS = ExtraWarningsForSystemJS(); + + bool isSystem = nsContentUtils::IsSystemPrincipal(aPrincipal); + + if (isSystem) { + // Make sure [SecureContext] APIs are visible: + aOptions.creationOptions().setSecureContext(true); + } + + if (shouldDiscardSystemSource) { + bool discardSource = isSystem; + + aOptions.behaviors().setDiscardSource(discardSource); + } + + if (extraWarningsForSystemJS) { + if (isSystem) + aOptions.behaviors().extraWarningsOverride().set(true); + } +} + +bool +InitGlobalObject(JSContext* aJSContext, JS::Handle aGlobal, uint32_t aFlags) +{ + // Immediately enter the global's compartment so that everything we create + // ends up there. + JSAutoCompartment ac(aJSContext, aGlobal); + + // Stuff coming through this path always ends up as a DOM global. + MOZ_ASSERT(js::GetObjectClass(aGlobal)->flags & JSCLASS_DOM_GLOBAL); + + if (!(aFlags & nsIXPConnect::OMIT_COMPONENTS_OBJECT)) { + // XPCCallContext gives us an active request needed to save/restore. + if (!CompartmentPrivate::Get(aGlobal)->scope->AttachComponentsObject(aJSContext) || + !XPCNativeWrapper::AttachNewConstructorObject(aJSContext, aGlobal)) { + return UnexpectedFailure(false); + } + } + + if (!(aFlags & nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK)) + JS_FireOnNewGlobalObject(aJSContext, aGlobal); + + return true; +} + +} // namespace xpc + +NS_IMETHODIMP +nsXPConnect::InitClassesWithNewWrappedGlobal(JSContext * aJSContext, + nsISupports* aCOMObj, + nsIPrincipal * aPrincipal, + uint32_t aFlags, + JS::CompartmentOptions& aOptions, + nsIXPConnectJSObjectHolder** _retval) +{ + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aCOMObj, "bad param"); + MOZ_ASSERT(_retval, "bad param"); + + // We pass null for the 'extra' pointer during global object creation, so + // we need to have a principal. + MOZ_ASSERT(aPrincipal); + + InitGlobalObjectOptions(aOptions, aPrincipal); + + // Call into XPCWrappedNative to make a new global object, scope, and global + // prototype. + xpcObjectHelper helper(aCOMObj); + MOZ_ASSERT(helper.GetScriptableFlags() & nsIXPCScriptable::IS_GLOBAL_OBJECT); + RefPtr wrappedGlobal; + nsresult rv = + XPCWrappedNative::WrapNewGlobal(helper, aPrincipal, + aFlags & nsIXPConnect::INIT_JS_STANDARD_CLASSES, + aOptions, getter_AddRefs(wrappedGlobal)); + NS_ENSURE_SUCCESS(rv, rv); + + // Grab a copy of the global and enter its compartment. + RootedObject global(aJSContext, wrappedGlobal->GetFlatJSObject()); + MOZ_ASSERT(JS_IsGlobalObject(global)); + + if (!InitGlobalObject(aJSContext, global, aFlags)) + return UnexpectedFailure(NS_ERROR_FAILURE); + + wrappedGlobal.forget(_retval); + return NS_OK; +} + +static nsresult +NativeInterface2JSObject(HandleObject aScope, + nsISupports* aCOMObj, + nsWrapperCache* aCache, + const nsIID * aIID, + bool aAllowWrapping, + MutableHandleValue aVal, + nsIXPConnectJSObjectHolder** aHolder) +{ + AutoJSContext cx; + JSAutoCompartment ac(cx, aScope); + + nsresult rv; + xpcObjectHelper helper(aCOMObj, aCache); + if (!XPCConvert::NativeInterface2JSObject(aVal, aHolder, helper, aIID, + aAllowWrapping, &rv)) + return rv; + + MOZ_ASSERT(aAllowWrapping || !xpc::WrapperFactory::IsXrayWrapper(&aVal.toObject()), + "Shouldn't be returning a xray wrapper here"); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::WrapNative(JSContext * aJSContext, + JSObject * aScopeArg, + nsISupports* aCOMObj, + const nsIID & aIID, + JSObject** aRetVal) +{ + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aScopeArg, "bad param"); + MOZ_ASSERT(aCOMObj, "bad param"); + + RootedObject aScope(aJSContext, aScopeArg); + RootedValue v(aJSContext); + nsresult rv = NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID, + true, &v, nullptr); + if (NS_FAILED(rv)) + return rv; + + if (!v.isObjectOrNull()) + return NS_ERROR_FAILURE; + + *aRetVal = v.toObjectOrNull(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::WrapNativeHolder(JSContext * aJSContext, + JSObject * aScopeArg, + nsISupports* aCOMObj, + const nsIID & aIID, + nsIXPConnectJSObjectHolder **aHolder) +{ + MOZ_ASSERT(aHolder, "bad param"); + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aScopeArg, "bad param"); + MOZ_ASSERT(aCOMObj, "bad param"); + + RootedObject aScope(aJSContext, aScopeArg); + RootedValue v(aJSContext); + return NativeInterface2JSObject(aScope, aCOMObj, nullptr, &aIID, + true, &v, aHolder); +} + +NS_IMETHODIMP +nsXPConnect::WrapNativeToJSVal(JSContext* aJSContext, + JSObject* aScopeArg, + nsISupports* aCOMObj, + nsWrapperCache* aCache, + const nsIID* aIID, + bool aAllowWrapping, + MutableHandleValue aVal) +{ + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aScopeArg, "bad param"); + MOZ_ASSERT(aCOMObj, "bad param"); + + RootedObject aScope(aJSContext, aScopeArg); + return NativeInterface2JSObject(aScope, aCOMObj, aCache, aIID, + aAllowWrapping, aVal, nullptr); +} + +NS_IMETHODIMP +nsXPConnect::WrapJS(JSContext * aJSContext, + JSObject * aJSObjArg, + const nsIID & aIID, + void * *result) +{ + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aJSObjArg, "bad param"); + MOZ_ASSERT(result, "bad param"); + + *result = nullptr; + + RootedObject aJSObj(aJSContext, aJSObjArg); + JSAutoCompartment ac(aJSContext, aJSObj); + + nsresult rv = NS_ERROR_UNEXPECTED; + if (!XPCConvert::JSObject2NativeInterface(result, aJSObj, + &aIID, nullptr, &rv)) + return rv; + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::JSValToVariant(JSContext* cx, + HandleValue aJSVal, + nsIVariant** aResult) +{ + NS_PRECONDITION(aResult, "bad param"); + + RefPtr variant = XPCVariant::newVariant(cx, aJSVal); + variant.forget(aResult); + NS_ENSURE_TRUE(*aResult, NS_ERROR_OUT_OF_MEMORY); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::WrapJSAggregatedToNative(nsISupports* aOuter, + JSContext* aJSContext, + JSObject* aJSObjArg, + const nsIID& aIID, + void** result) +{ + MOZ_ASSERT(aOuter, "bad param"); + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aJSObjArg, "bad param"); + MOZ_ASSERT(result, "bad param"); + + *result = nullptr; + + RootedObject aJSObj(aJSContext, aJSObjArg); + nsresult rv; + if (!XPCConvert::JSObject2NativeInterface(result, aJSObj, + &aIID, aOuter, &rv)) + return rv; + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::GetWrappedNativeOfJSObject(JSContext * aJSContext, + JSObject * aJSObjArg, + nsIXPConnectWrappedNative** _retval) +{ + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aJSObjArg, "bad param"); + MOZ_ASSERT(_retval, "bad param"); + + RootedObject aJSObj(aJSContext, aJSObjArg); + aJSObj = js::CheckedUnwrap(aJSObj, /* stopAtWindowProxy = */ false); + if (!aJSObj || !IS_WN_REFLECTOR(aJSObj)) { + *_retval = nullptr; + return NS_ERROR_FAILURE; + } + + RefPtr temp = XPCWrappedNative::Get(aJSObj); + temp.forget(_retval); + return NS_OK; +} + +already_AddRefed +xpc::UnwrapReflectorToISupports(JSObject* reflector) +{ + // Unwrap security wrappers, if allowed. + reflector = js::CheckedUnwrap(reflector, /* stopAtWindowProxy = */ false); + if (!reflector) + return nullptr; + + // Try XPCWrappedNatives. + if (IS_WN_REFLECTOR(reflector)) { + XPCWrappedNative* wn = XPCWrappedNative::Get(reflector); + if (!wn) + return nullptr; + nsCOMPtr native = wn->Native(); + return native.forget(); + } + + // Try DOM objects. This QI without taking a ref first is safe, because + // this if non-null our thing will definitely be a DOM object, and we know + // their QI to nsISupports doesn't do anything weird. + nsCOMPtr canonical = + do_QueryInterface(mozilla::dom::UnwrapDOMObjectToISupports(reflector)); + return canonical.forget(); +} + +NS_IMETHODIMP +nsXPConnect::GetWrappedNativeOfNativeObject(JSContext * aJSContext, + JSObject * aScopeArg, + nsISupports* aCOMObj, + const nsIID & aIID, + nsIXPConnectWrappedNative** _retval) +{ + MOZ_ASSERT(aJSContext, "bad param"); + MOZ_ASSERT(aScopeArg, "bad param"); + MOZ_ASSERT(aCOMObj, "bad param"); + MOZ_ASSERT(_retval, "bad param"); + + *_retval = nullptr; + + RootedObject aScope(aJSContext, aScopeArg); + + XPCWrappedNativeScope* scope = ObjectScope(aScope); + if (!scope) + return UnexpectedFailure(NS_ERROR_FAILURE); + + RefPtr iface = + XPCNativeInterface::GetNewOrUsed(&aIID); + if (!iface) + return NS_ERROR_FAILURE; + + XPCWrappedNative* wrapper; + + nsresult rv = XPCWrappedNative::GetUsedOnly(aCOMObj, scope, iface, &wrapper); + if (NS_FAILED(rv)) + return NS_ERROR_FAILURE; + *_retval = static_cast(wrapper); + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::GetCurrentJSStack(nsIStackFrame * *aCurrentJSStack) +{ + MOZ_ASSERT(aCurrentJSStack, "bad param"); + + nsCOMPtr currentStack = dom::GetCurrentJSStack(); + currentStack.forget(aCurrentJSStack); + + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::GetCurrentNativeCallContext(nsAXPCNativeCallContext * *aCurrentNativeCallContext) +{ + MOZ_ASSERT(aCurrentNativeCallContext, "bad param"); + + *aCurrentNativeCallContext = XPCJSContext::Get()->GetCallContext(); + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::SetFunctionThisTranslator(const nsIID & aIID, + nsIXPCFunctionThisTranslator* aTranslator) +{ + XPCJSContext* cx = GetContext(); + IID2ThisTranslatorMap* map = cx->GetThisTranslatorMap(); + map->Add(aIID, aTranslator); + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::CreateSandbox(JSContext* cx, nsIPrincipal* principal, + JSObject** _retval) +{ + *_retval = nullptr; + + RootedValue rval(cx); + SandboxOptions options; + nsresult rv = CreateSandboxObject(cx, &rval, principal, options); + MOZ_ASSERT(NS_FAILED(rv) || !rval.isPrimitive(), + "Bad return value from xpc_CreateSandboxObject()!"); + + if (NS_SUCCEEDED(rv) && !rval.isPrimitive()) { + *_retval = rval.toObjectOrNull(); + } + + return rv; +} + +NS_IMETHODIMP +nsXPConnect::EvalInSandboxObject(const nsAString& source, const char* filename, + JSContext* cx, JSObject* sandboxArg, + int32_t jsVersion, + MutableHandleValue rval) +{ +#ifdef DEBUG + { + const char *version = JS_VersionToString(JSVersion(jsVersion)); + MOZ_ASSERT(version && strcmp(version, "unknown") != 0, "Illegal JS version passed"); + } +#endif + if (!sandboxArg) + return NS_ERROR_INVALID_ARG; + + RootedObject sandbox(cx, sandboxArg); + nsCString filenameStr; + if (filename) { + filenameStr.Assign(filename); + } else { + filenameStr = NS_LITERAL_CSTRING("x-bogus://XPConnect/Sandbox"); + } + return EvalInSandbox(cx, sandbox, source, filenameStr, 1, + JSVersion(jsVersion), rval); +} + +NS_IMETHODIMP +nsXPConnect::GetWrappedNativePrototype(JSContext* aJSContext, + JSObject* aScopeArg, + nsIClassInfo* aClassInfo, + JSObject** aRetVal) +{ + RootedObject aScope(aJSContext, aScopeArg); + JSAutoCompartment ac(aJSContext, aScope); + + XPCWrappedNativeScope* scope = ObjectScope(aScope); + if (!scope) + return UnexpectedFailure(NS_ERROR_FAILURE); + + XPCNativeScriptableCreateInfo sciProto; + XPCWrappedNative::GatherProtoScriptableCreateInfo(aClassInfo, sciProto); + + AutoMarkingWrappedNativeProtoPtr proto(aJSContext); + proto = XPCWrappedNativeProto::GetNewOrUsed(scope, aClassInfo, &sciProto); + if (!proto) + return UnexpectedFailure(NS_ERROR_FAILURE); + + JSObject* protoObj = proto->GetJSProtoObject(); + if (!protoObj) + return UnexpectedFailure(NS_ERROR_FAILURE); + + *aRetVal = protoObj; + + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::DebugDump(int16_t depth) +{ +#ifdef DEBUG + depth-- ; + XPC_LOG_ALWAYS(("nsXPConnect @ %x with mRefCnt = %d", this, mRefCnt.get())); + XPC_LOG_INDENT(); + XPC_LOG_ALWAYS(("gSelf @ %x", gSelf)); + XPC_LOG_ALWAYS(("gOnceAliveNowDead is %d", (int)gOnceAliveNowDead)); + if (mContext) { + if (depth) + mContext->DebugDump(depth); + else + XPC_LOG_ALWAYS(("XPCJSContext @ %x", mContext)); + } else + XPC_LOG_ALWAYS(("mContext is null")); + XPCWrappedNativeScope::DebugDumpAllScopes(depth); + XPC_LOG_OUTDENT(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::DebugDumpObject(nsISupports* p, int16_t depth) +{ +#ifdef DEBUG + if (!depth) + return NS_OK; + if (!p) { + XPC_LOG_ALWAYS(("*** Cound not dump object with NULL address")); + return NS_OK; + } + + nsCOMPtr xpc; + nsCOMPtr wjsc; + nsCOMPtr wn; + nsCOMPtr wjs; + + if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnect), + getter_AddRefs(xpc)))) { + XPC_LOG_ALWAYS(("Dumping a nsIXPConnect...")); + xpc->DebugDump(depth); + } else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPCWrappedJSClass), + getter_AddRefs(wjsc)))) { + XPC_LOG_ALWAYS(("Dumping a nsIXPCWrappedJSClass...")); + wjsc->DebugDump(depth); + } else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnectWrappedNative), + getter_AddRefs(wn)))) { + XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedNative...")); + wn->DebugDump(depth); + } else if (NS_SUCCEEDED(p->QueryInterface(NS_GET_IID(nsIXPConnectWrappedJS), + getter_AddRefs(wjs)))) { + XPC_LOG_ALWAYS(("Dumping a nsIXPConnectWrappedJS...")); + wjs->DebugDump(depth); + } else { + XPC_LOG_ALWAYS(("*** Could not dump the nsISupports @ %x", p)); + } +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::DebugDumpJSStack(bool showArgs, + bool showLocals, + bool showThisProps) +{ + xpc_DumpJSStack(showArgs, showLocals, showThisProps); + + return NS_OK; +} + +char* +nsXPConnect::DebugPrintJSStack(bool showArgs, + bool showLocals, + bool showThisProps) +{ + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (!cx) + printf("there is no JSContext on the nsIThreadJSContextStack!\n"); + else + return xpc_PrintJSStack(cx, showArgs, showLocals, showThisProps); + + return nullptr; +} + +NS_IMETHODIMP +nsXPConnect::VariantToJS(JSContext* ctx, JSObject* scopeArg, nsIVariant* value, + MutableHandleValue _retval) +{ + NS_PRECONDITION(ctx, "bad param"); + NS_PRECONDITION(scopeArg, "bad param"); + NS_PRECONDITION(value, "bad param"); + + RootedObject scope(ctx, scopeArg); + MOZ_ASSERT(js::IsObjectInContextCompartment(scope, ctx)); + + nsresult rv = NS_OK; + if (!XPCVariant::VariantDataToJS(value, &rv, _retval)) { + if (NS_FAILED(rv)) + return rv; + + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXPConnect::JSToVariant(JSContext* ctx, HandleValue value, nsIVariant** _retval) +{ + NS_PRECONDITION(ctx, "bad param"); + NS_PRECONDITION(_retval, "bad param"); + + RefPtr variant = XPCVariant::newVariant(ctx, value); + variant.forget(_retval); + if (!(*_retval)) + return NS_ERROR_FAILURE; + + return NS_OK; +} + +nsIPrincipal* +nsXPConnect::GetPrincipal(JSObject* obj, bool allowShortCircuit) const +{ + MOZ_ASSERT(IS_WN_REFLECTOR(obj), "What kind of wrapper is this?"); + + XPCWrappedNative* xpcWrapper = XPCWrappedNative::Get(obj); + if (xpcWrapper) { + if (allowShortCircuit) { + nsIPrincipal* result = xpcWrapper->GetObjectPrincipal(); + if (result) { + return result; + } + } + + // If not, check if it points to an nsIScriptObjectPrincipal + nsCOMPtr objPrin = + do_QueryInterface(xpcWrapper->Native()); + if (objPrin) { + nsIPrincipal* result = objPrin->GetPrincipal(); + if (result) { + return result; + } + } + } + + return nullptr; +} + +namespace xpc { + +bool +Base64Encode(JSContext* cx, HandleValue val, MutableHandleValue out) +{ + MOZ_ASSERT(cx); + + nsAutoCString encodedString; + if (!ConvertJSValueToByteString(cx, val, false, encodedString)) { + return false; + } + + nsAutoCString result; + if (NS_FAILED(mozilla::Base64Encode(encodedString, result))) { + JS_ReportErrorASCII(cx, "Failed to encode base64 data!"); + return false; + } + + JSString* str = JS_NewStringCopyN(cx, result.get(), result.Length()); + if (!str) + return false; + + out.setString(str); + return true; +} + +bool +Base64Decode(JSContext* cx, HandleValue val, MutableHandleValue out) +{ + MOZ_ASSERT(cx); + + nsAutoCString encodedString; + if (!ConvertJSValueToByteString(cx, val, false, encodedString)) { + return false; + } + + nsAutoCString result; + if (NS_FAILED(mozilla::Base64Decode(encodedString, result))) { + JS_ReportErrorASCII(cx, "Failed to decode base64 string!"); + return false; + } + + JSString* str = JS_NewStringCopyN(cx, result.get(), result.Length()); + if (!str) + return false; + + out.setString(str); + return true; +} + +void +SetLocationForGlobal(JSObject* global, const nsACString& location) +{ + MOZ_ASSERT(global); + CompartmentPrivate::Get(global)->SetLocation(location); +} + +void +SetLocationForGlobal(JSObject* global, nsIURI* locationURI) +{ + MOZ_ASSERT(global); + CompartmentPrivate::Get(global)->SetLocationURI(locationURI); +} + +} // namespace xpc + +NS_IMETHODIMP +nsXPConnect::NotifyDidPaint() +{ + JS::NotifyDidPaint(GetContext()->Context()); + return NS_OK; +} + +static nsresult +WriteScriptOrFunction(nsIObjectOutputStream* stream, JSContext* cx, + JSScript* scriptArg, HandleObject functionObj) +{ + // Exactly one of script or functionObj must be given + MOZ_ASSERT(!scriptArg != !functionObj); + + RootedScript script(cx, scriptArg); + if (!script) { + RootedFunction fun(cx, JS_GetObjectFunction(functionObj)); + script.set(JS_GetFunctionScript(cx, fun)); + } + + uint8_t flags = 0; // We don't have flags anymore. + nsresult rv = stream->Write8(flags); + if (NS_FAILED(rv)) + return rv; + + + TranscodeBuffer buffer; + TranscodeResult code; + { + if (functionObj) + code = EncodeInterpretedFunction(cx, buffer, functionObj); + else + code = EncodeScript(cx, buffer, script); + } + + if (code != TranscodeResult_Ok) { + if ((code & TranscodeResult_Failure) != 0) + return NS_ERROR_FAILURE; + MOZ_ASSERT((code & TranscodeResult_Throw) != 0); + JS_ClearPendingException(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + + size_t size = buffer.length(); + if (size > UINT32_MAX) + return NS_ERROR_FAILURE; + rv = stream->Write32(size); + if (NS_SUCCEEDED(rv)) + rv = stream->WriteBytes(reinterpret_cast(buffer.begin()), size); + + return rv; +} + +static nsresult +ReadScriptOrFunction(nsIObjectInputStream* stream, JSContext* cx, + JSScript** scriptp, JSObject** functionObjp) +{ + // Exactly one of script or functionObj must be given + MOZ_ASSERT(!scriptp != !functionObjp); + + uint8_t flags; + nsresult rv = stream->Read8(&flags); + if (NS_FAILED(rv)) + return rv; + + // We don't serialize mutedError-ness of scripts, which is fine as long as + // we only serialize system and XUL-y things. We can detect this by checking + // where the caller wants us to deserialize. + MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome() || + CurrentGlobalOrNull(cx) == xpc::CompilationScope()); + + uint32_t size; + rv = stream->Read32(&size); + if (NS_FAILED(rv)) + return rv; + + char* data; + rv = stream->ReadBytes(size, &data); + if (NS_FAILED(rv)) + return rv; + + TranscodeBuffer buffer; + buffer.replaceRawBuffer(reinterpret_cast(data), size); + + { + TranscodeResult code; + if (scriptp) { + Rooted script(cx); + code = DecodeScript(cx, buffer, &script); + if (code == TranscodeResult_Ok) + *scriptp = script.get(); + } else { + Rooted funobj(cx); + code = DecodeInterpretedFunction(cx, buffer, &funobj); + if (code == TranscodeResult_Ok) + *functionObjp = JS_GetFunctionObject(funobj.get()); + } + + if (code != TranscodeResult_Ok) { + if ((code & TranscodeResult_Failure) != 0) + return NS_ERROR_FAILURE; + MOZ_ASSERT((code & TranscodeResult_Throw) != 0); + JS_ClearPendingException(cx); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return rv; +} + +NS_IMETHODIMP +nsXPConnect::WriteScript(nsIObjectOutputStream* stream, JSContext* cx, JSScript* script) +{ + return WriteScriptOrFunction(stream, cx, script, nullptr); +} + +NS_IMETHODIMP +nsXPConnect::ReadScript(nsIObjectInputStream* stream, JSContext* cx, JSScript** scriptp) +{ + return ReadScriptOrFunction(stream, cx, scriptp, nullptr); +} + +NS_IMETHODIMP +nsXPConnect::WriteFunction(nsIObjectOutputStream* stream, JSContext* cx, JSObject* functionObjArg) +{ + RootedObject functionObj(cx, functionObjArg); + return WriteScriptOrFunction(stream, cx, nullptr, functionObj); +} + +NS_IMETHODIMP +nsXPConnect::ReadFunction(nsIObjectInputStream* stream, JSContext* cx, JSObject** functionObjp) +{ + return ReadScriptOrFunction(stream, cx, nullptr, functionObjp); +} + +/* These are here to be callable from a debugger */ +extern "C" { +JS_EXPORT_API(void) DumpJSStack() +{ + xpc_DumpJSStack(true, true, false); +} + +JS_EXPORT_API(char*) PrintJSStack() +{ + nsresult rv; + nsCOMPtr xpc(do_GetService(nsIXPConnect::GetCID(), &rv)); + return (NS_SUCCEEDED(rv) && xpc) ? + xpc->DebugPrintJSStack(true, true, false) : + nullptr; +} + +JS_EXPORT_API(void) DumpCompleteHeap() +{ + nsCOMPtr listener = + do_CreateInstance("@mozilla.org/cycle-collector-logger;1"); + if (!listener) { + NS_WARNING("Failed to create CC logger"); + return; + } + + nsCOMPtr alltracesListener; + listener->AllTraces(getter_AddRefs(alltracesListener)); + if (!alltracesListener) { + NS_WARNING("Failed to get all traces logger"); + return; + } + + nsJSContext::CycleCollectNow(alltracesListener); +} + +} // extern "C" + +namespace xpc { + +bool +Atob(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.length()) + return true; + + return xpc::Base64Decode(cx, args[0], args.rval()); +} + +bool +Btoa(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.length()) + return true; + + return xpc::Base64Encode(cx, args[0], args.rval()); +} + +bool +IsXrayWrapper(JSObject* obj) +{ + return WrapperFactory::IsXrayWrapper(obj); +} + +JSAddonId* +NewAddonId(JSContext* cx, const nsACString& id) +{ + JS::RootedString str(cx, JS_NewStringCopyN(cx, id.BeginReading(), id.Length())); + if (!str) + return nullptr; + return JS::NewAddonId(cx, str); +} + +bool +SetAddonInterposition(const nsACString& addonIdStr, nsIAddonInterposition* interposition) +{ + JSAddonId* addonId; + // We enter the junk scope just to allocate a string, which actually will go + // in the system zone. + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::PrivilegedJunkScope())) + return false; + addonId = NewAddonId(jsapi.cx(), addonIdStr); + if (!addonId) + return false; + return XPCWrappedNativeScope::SetAddonInterposition(jsapi.cx(), addonId, interposition); +} + +bool +AllowCPOWsInAddon(const nsACString& addonIdStr, bool allow) +{ + JSAddonId* addonId; + // We enter the junk scope just to allocate a string, which actually will go + // in the system zone. + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::PrivilegedJunkScope())) + return false; + addonId = NewAddonId(jsapi.cx(), addonIdStr); + if (!addonId) + return false; + return XPCWrappedNativeScope::AllowCPOWsInAddon(jsapi.cx(), addonId, allow); +} + +} // namespace xpc + +namespace mozilla { +namespace dom { + +bool +IsChromeOrXBL(JSContext* cx, JSObject* /* unused */) +{ + MOZ_ASSERT(NS_IsMainThread()); + JSCompartment* c = js::GetContextCompartment(cx); + + // For remote XUL, we run XBL in the XUL scope. Given that we care about + // compat and not security for remote XUL, we just always claim to be XBL. + // + // Note that, for performance, we don't check AllowXULXBLForPrincipal here, + // and instead rely on the fact that AllowContentXBLScope() only returns false in + // remote XUL situations. + return AccessCheck::isChrome(c) || IsContentXBLScope(c) || !AllowContentXBLScope(c); +} + +namespace workers { +extern bool IsCurrentThreadRunningChromeWorker(); +} // namespace workers + +bool +ThreadSafeIsChromeOrXBL(JSContext* cx, JSObject* obj) +{ + if (NS_IsMainThread()) { + return IsChromeOrXBL(cx, obj); + } + return workers::IsCurrentThreadRunningChromeWorker(); +} + +} // namespace dom +} // namespace mozilla diff --git a/js/xpconnect/src/qsObjectHelper.h b/js/xpconnect/src/qsObjectHelper.h new file mode 100644 index 000000000..a3c753800 --- /dev/null +++ b/js/xpconnect/src/qsObjectHelper.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef qsObjectHelper_h +#define qsObjectHelper_h + +#include "xpcObjectHelper.h" + +#include "nsCOMPtr.h" +#include "nsWrapperCache.h" +#include "mozilla/TypeTraits.h" + +class qsObjectHelper : public xpcObjectHelper +{ +public: + template + inline + qsObjectHelper(T* aObject, nsWrapperCache* aCache) + : xpcObjectHelper(ToSupports(aObject), ToCanonicalSupports(aObject), + aCache) + {} + + template + inline + qsObjectHelper(nsCOMPtr& aObject, nsWrapperCache* aCache) + : xpcObjectHelper(ToSupports(aObject.get()), + ToCanonicalSupports(aObject.get()), aCache) + { + if (mCanonical) { + // Transfer the strong reference. + mCanonicalStrong = dont_AddRef(mCanonical); + aObject.forget(); + } + } + + template + inline + qsObjectHelper(RefPtr& aObject, nsWrapperCache* aCache) + : xpcObjectHelper(ToSupports(aObject.get()), + ToCanonicalSupports(aObject.get()), aCache) + { + if (mCanonical) { + // Transfer the strong reference. + mCanonicalStrong = dont_AddRef(mCanonical); + aObject.forget(); + } + } +}; + +#endif diff --git a/js/xpconnect/src/xpc.msg b/js/xpconnect/src/xpc.msg new file mode 100644 index 000000000..182cdbba8 --- /dev/null +++ b/js/xpconnect/src/xpc.msg @@ -0,0 +1,228 @@ +/* -*- 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/. */ + +/* Error Message definitions. */ + + +/* xpconnect specific codes (from nsIXPConnect.h) */ + +XPC_MSG_DEF(NS_ERROR_XPC_NOT_ENOUGH_ARGS , "Not enough arguments") +XPC_MSG_DEF(NS_ERROR_XPC_NEED_OUT_OBJECT , "'Out' argument must be an object") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_OUT_VAL , "Cannot set 'value' property of 'out' argument") +XPC_MSG_DEF(NS_ERROR_XPC_NATIVE_RETURNED_FAILURE , "Component returned failure code:") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_INTERFACE_INFO , "Cannot find interface information") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO , "Cannot find interface information for parameter") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_METHOD_INFO , "Cannot find method information") +XPC_MSG_DEF(NS_ERROR_XPC_UNEXPECTED , "Unexpected error in XPConnect") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_JS , "Could not convert JavaScript argument") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_NATIVE , "Could not convert Native argument") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF , "Could not convert JavaScript argument (NULL value cannot be used for a C++ reference type)") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO , "Illegal operation on WrappedNative prototype object") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN , "Cannot convert WrappedNative to function") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN , "Cannot define new property in a WrappedNative") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_WATCH_WN_STATIC , "Cannot place watchpoints on WrappedNative object static properties") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_EXPORT_WN_STATIC , "Cannot export a WrappedNative object's static properties") +XPC_MSG_DEF(NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED , "nsIXPCScriptable::Call failed") +XPC_MSG_DEF(NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED , "nsIXPCScriptable::Construct failed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE , "Cannot use wrapper as function unless it implements nsIXPCScriptable") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE , "Cannot use wrapper as constructor unless it implements nsIXPCScriptable") +XPC_MSG_DEF(NS_ERROR_XPC_CI_RETURNED_FAILURE , "ComponentManager::CreateInstance returned failure code:") +XPC_MSG_DEF(NS_ERROR_XPC_GS_RETURNED_FAILURE , "ServiceManager::GetService returned failure code:") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CID , "Invalid ClassID or ContractID") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_IID , "Invalid InterfaceID") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CREATE_WN , "Cannot create wrapper around native interface") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_EXCEPTION , "JavaScript component threw exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT , "JavaScript component threw a native object that is not an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_JS_OBJECT , "JavaScript component threw a JavaScript object") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_NULL , "JavaScript component threw a null value as an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_STRING , "JavaScript component threw a string as an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JS_THREW_NUMBER , "JavaScript component threw a number as an exception") +XPC_MSG_DEF(NS_ERROR_XPC_JAVASCRIPT_ERROR , "JavaScript component caused a JavaScript error") +XPC_MSG_DEF(NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS , "JavaScript component caused a JavaScript error (detailed report attached)") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY, "Cannot convert primitive JavaScript value into an array") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY , "Cannot convert JavaScript object into an array") +XPC_MSG_DEF(NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY , "JavaScript Array does not have as many elements as indicated by size argument") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_ARRAY_INFO , "Cannot find array information") +XPC_MSG_DEF(NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING , "JavaScript String does not have as many characters as indicated by size argument") +XPC_MSG_DEF(NS_ERROR_XPC_SECURITY_MANAGER_VETO , "Security Manager vetoed action") +XPC_MSG_DEF(NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE , "Failed to build a wrapper because the interface that was not declared [scriptable]") +XPC_MSG_DEF(NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS , "Failed to build a wrapper because the interface does not inherit from nsISupports") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_GET_JSOBJECT_OF_DOM_OBJECT, "Cannot get JavaScript object for DOM object") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT , "Property is a constant and cannot be changed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE , "Property is a read only attribute and cannot be changed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD , "Property is an interface method and cannot be changed") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE, "Cannot add property to WrappedNative object") +XPC_MSG_DEF(NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED , "Call to nsIXPCScriptable interface for WrappedNative failed unexpecedly") +XPC_MSG_DEF(NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED , "JavaScript component does not have a method named:") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_ID_STRING , "Bad ID string") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_INITIALIZER_NAME , "Bad initializer name in Constructor - Component has no method with that name") +XPC_MSG_DEF(NS_ERROR_XPC_HAS_BEEN_SHUTDOWN , "Operation failed because the XPConnect subsystem has been shutdown") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN , "Cannot modify properties of a WrappedNative") +XPC_MSG_DEF(NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL , "Could not convert JavaScript argument - 0 was passed, expected object. Did you mean null?") +XPC_MSG_DEF(NS_ERROR_XPC_CANT_PASS_CPOW_TO_NATIVE , "It's illegal to pass a CPOW to native code") + + +/* common global codes (from nsError.h) */ + +XPC_MSG_DEF(NS_OK , "Success") +XPC_MSG_DEF(NS_ERROR_NOT_INITIALIZED , "Component not initialized") +XPC_MSG_DEF(NS_ERROR_ALREADY_INITIALIZED , "Component already initialized") +XPC_MSG_DEF(NS_ERROR_NOT_IMPLEMENTED , "Method not implemented") +XPC_MSG_DEF(NS_NOINTERFACE , "Component does not have requested interface") +XPC_MSG_DEF(NS_ERROR_NO_INTERFACE , "Component does not have requested interface") +XPC_MSG_DEF(NS_ERROR_ILLEGAL_VALUE , "Illegal value") +XPC_MSG_DEF(NS_ERROR_INVALID_POINTER , "Invalid pointer") +XPC_MSG_DEF(NS_ERROR_NULL_POINTER , "Null pointer") +XPC_MSG_DEF(NS_ERROR_ABORT , "Abort") +XPC_MSG_DEF(NS_ERROR_FAILURE , "Failure") +XPC_MSG_DEF(NS_ERROR_UNEXPECTED , "Unexpected error") +XPC_MSG_DEF(NS_ERROR_OUT_OF_MEMORY , "Out of Memory") +XPC_MSG_DEF(NS_ERROR_INVALID_ARG , "Invalid argument") +XPC_MSG_DEF(NS_ERROR_NO_AGGREGATION , "Component does not support aggregation") +XPC_MSG_DEF(NS_ERROR_NOT_AVAILABLE , "Component is not available") +XPC_MSG_DEF(NS_ERROR_FACTORY_NOT_REGISTERED , "Factory not registered") +XPC_MSG_DEF(NS_ERROR_FACTORY_REGISTER_AGAIN , "Factory not registered (may be tried again)") +XPC_MSG_DEF(NS_ERROR_FACTORY_NOT_LOADED , "Factory not loaded") +XPC_MSG_DEF(NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT , "Factory does not support signatures") +XPC_MSG_DEF(NS_ERROR_FACTORY_EXISTS , "Factory already exists") + +/* added from nsError.h on Feb 28 2001... */ + +XPC_MSG_DEF(NS_BASE_STREAM_CLOSED , "Stream closed") +XPC_MSG_DEF(NS_BASE_STREAM_OSERROR , "Error from the operating system") +XPC_MSG_DEF(NS_BASE_STREAM_ILLEGAL_ARGS , "Illegal arguments") +XPC_MSG_DEF(NS_BASE_STREAM_NO_CONVERTER , "No converter for unichar streams") +XPC_MSG_DEF(NS_BASE_STREAM_BAD_CONVERSION , "Bad converter for unichar streams") +XPC_MSG_DEF(NS_BASE_STREAM_WOULD_BLOCK , "Stream would block") + +XPC_MSG_DEF(NS_ERROR_FILE_UNRECOGNIZED_PATH , "File error: Unrecognized path") +XPC_MSG_DEF(NS_ERROR_FILE_UNRESOLVABLE_SYMLINK , "File error: Unresolvable symlink") +XPC_MSG_DEF(NS_ERROR_FILE_EXECUTION_FAILED , "File error: Execution failed") +XPC_MSG_DEF(NS_ERROR_FILE_UNKNOWN_TYPE , "File error: Unknown type") +XPC_MSG_DEF(NS_ERROR_FILE_DESTINATION_NOT_DIR , "File error: Destination not dir") +XPC_MSG_DEF(NS_ERROR_FILE_TARGET_DOES_NOT_EXIST , "File error: Target does not exist") +XPC_MSG_DEF(NS_ERROR_FILE_COPY_OR_MOVE_FAILED , "File error: Copy or move failed") +XPC_MSG_DEF(NS_ERROR_FILE_ALREADY_EXISTS , "File error: Already exists") +XPC_MSG_DEF(NS_ERROR_FILE_INVALID_PATH , "File error: Invalid path") +XPC_MSG_DEF(NS_ERROR_FILE_DISK_FULL , "File error: Disk full") +XPC_MSG_DEF(NS_ERROR_FILE_CORRUPTED , "File error: Corrupted") +XPC_MSG_DEF(NS_ERROR_FILE_NOT_DIRECTORY , "File error: Not directory") +XPC_MSG_DEF(NS_ERROR_FILE_IS_DIRECTORY , "File error: Is directory") +XPC_MSG_DEF(NS_ERROR_FILE_IS_LOCKED , "File error: Is locked") +XPC_MSG_DEF(NS_ERROR_FILE_TOO_BIG , "File error: Too big") +XPC_MSG_DEF(NS_ERROR_FILE_NO_DEVICE_SPACE , "File error: No device space") +XPC_MSG_DEF(NS_ERROR_FILE_NAME_TOO_LONG , "File error: Name too long") +XPC_MSG_DEF(NS_ERROR_FILE_NOT_FOUND , "File error: Not found") +XPC_MSG_DEF(NS_ERROR_FILE_READ_ONLY , "File error: Read only") +XPC_MSG_DEF(NS_ERROR_FILE_DIR_NOT_EMPTY , "File error: Dir not empty") +XPC_MSG_DEF(NS_ERROR_FILE_ACCESS_DENIED , "File error: Access denied") + +/* added from nsError.h on Sept 6 2001... */ + +XPC_MSG_DEF(NS_ERROR_CANNOT_CONVERT_DATA , "Data conversion error") +XPC_MSG_DEF(NS_ERROR_OBJECT_IS_IMMUTABLE , "Can not modify immutable data container") +XPC_MSG_DEF(NS_ERROR_LOSS_OF_SIGNIFICANT_DATA , "Data conversion failed because significant data would be lost") +XPC_MSG_DEF(NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA , "Data conversion succeeded but data was rounded to fit") + +/* network related codes (from nsNetError.h) */ + +XPC_MSG_DEF(NS_BINDING_FAILED , "The async request failed for some unknown reason") +XPC_MSG_DEF(NS_BINDING_ABORTED , "The async request failed because it was aborted by some user action") +XPC_MSG_DEF(NS_BINDING_REDIRECTED , "The async request has been redirected to a different async request") +XPC_MSG_DEF(NS_BINDING_RETARGETED , "The async request has been retargeted to a different handler") +XPC_MSG_DEF(NS_ERROR_MALFORMED_URI , "The URI is malformed") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROTOCOL , "The URI scheme corresponds to an unknown protocol handler") +XPC_MSG_DEF(NS_ERROR_NO_CONTENT , "Channel opened successfully but no data will be returned") +XPC_MSG_DEF(NS_ERROR_IN_PROGRESS , "The requested action could not be completed while the object is busy") +XPC_MSG_DEF(NS_ERROR_ALREADY_OPENED , "Channel is already open") +XPC_MSG_DEF(NS_ERROR_INVALID_CONTENT_ENCODING , "The content encoding of the source document is incorrect") +XPC_MSG_DEF(NS_ERROR_CORRUPTED_CONTENT , "Corrupted content received from server (potentially MIME type mismatch because of 'X-Content-Type-Options: nosniff')") +XPC_MSG_DEF(NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY, "Couldn't extract first component from potentially corrupted header field") +XPC_MSG_DEF(NS_ERROR_ALREADY_CONNECTED , "The connection is already established") +XPC_MSG_DEF(NS_ERROR_NOT_CONNECTED , "The connection does not exist") +XPC_MSG_DEF(NS_ERROR_CONNECTION_REFUSED , "The connection was refused") +XPC_MSG_DEF(NS_ERROR_PROXY_CONNECTION_REFUSED , "The connection to the proxy server was refused") +XPC_MSG_DEF(NS_ERROR_NET_TIMEOUT , "The connection has timed out") +XPC_MSG_DEF(NS_ERROR_OFFLINE , "The requested action could not be completed in the offline state") +XPC_MSG_DEF(NS_ERROR_PORT_ACCESS_NOT_ALLOWED , "Establishing a connection to an unsafe or otherwise banned port was prohibited") +XPC_MSG_DEF(NS_ERROR_NET_RESET , "The connection was established, but no data was ever received") +XPC_MSG_DEF(NS_ERROR_NET_INTERRUPT , "The connection was established, but the data transfer was interrupted") +XPC_MSG_DEF(NS_ERROR_NET_PARTIAL_TRANSFER , "A transfer was only partially done when it completed") +XPC_MSG_DEF(NS_ERROR_NOT_RESUMABLE , "This request is not resumable, but it was tried to resume it, or to request resume-specific data") +XPC_MSG_DEF(NS_ERROR_ENTITY_CHANGED , "It was attempted to resume the request, but the entity has changed in the meantime") +XPC_MSG_DEF(NS_ERROR_REDIRECT_LOOP , "The request failed as a result of a detected redirection loop") +XPC_MSG_DEF(NS_ERROR_UNSAFE_CONTENT_TYPE , "The request failed because the content type returned by the server was not a type expected by the channel") +XPC_MSG_DEF(NS_ERROR_REMOTE_XUL , "Attempt to access remote XUL document that is not in website's whitelist") +XPC_MSG_DEF(NS_ERROR_LOAD_SHOWED_ERRORPAGE , "The load caused an error page to be displayed.") + +XPC_MSG_DEF(NS_ERROR_FTP_LOGIN , "FTP error while logging in") +XPC_MSG_DEF(NS_ERROR_FTP_CWD , "FTP error while changing directory") +XPC_MSG_DEF(NS_ERROR_FTP_PASV , "FTP error while changing to passive mode") +XPC_MSG_DEF(NS_ERROR_FTP_PWD , "FTP error while retrieving current directory") +XPC_MSG_DEF(NS_ERROR_FTP_LIST , "FTP error while retrieving a directory listing") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_HOST , "The lookup of the hostname failed") +XPC_MSG_DEF(NS_ERROR_DNS_LOOKUP_QUEUE_FULL , "The DNS lookup queue is full") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROXY_HOST , "The lookup of the proxy hostname failed") +XPC_MSG_DEF(NS_ERROR_UNKNOWN_SOCKET_TYPE , "The specified socket type does not exist") +XPC_MSG_DEF(NS_ERROR_SOCKET_CREATE_FAILED , "The specified socket type could not be created") +XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED , "The specified socket address type is not supported") +XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_IN_USE , "Some other socket is already using the specified address.") +XPC_MSG_DEF(NS_ERROR_CACHE_KEY_NOT_FOUND , "Cache key could not be found") +XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_STREAM , "Cache data is a stream") +XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_NOT_STREAM , "Cache data is not a stream") +XPC_MSG_DEF(NS_ERROR_CACHE_WAIT_FOR_VALIDATION , "Cache entry exists but needs to be validated first") +XPC_MSG_DEF(NS_ERROR_CACHE_ENTRY_DOOMED , "Cache entry has been doomed") +XPC_MSG_DEF(NS_ERROR_CACHE_READ_ACCESS_DENIED , "Read access to cache denied") +XPC_MSG_DEF(NS_ERROR_CACHE_WRITE_ACCESS_DENIED , "Write access to cache denied") +XPC_MSG_DEF(NS_ERROR_CACHE_IN_USE , "Cache is currently in use") +XPC_MSG_DEF(NS_ERROR_DOCUMENT_NOT_CACHED , "Document does not exist in cache") +XPC_MSG_DEF(NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS , "The requested number of domain levels exceeds those present in the host string") +XPC_MSG_DEF(NS_ERROR_HOST_IS_IP_ADDRESS , "The host string is an IP address") +XPC_MSG_DEF(NS_ERROR_NOT_SAME_THREAD , "Can't access a wrapped JS object from a different thread") + +/* storage related codes (from mozStorage.h) */ +XPC_MSG_DEF(NS_ERROR_STORAGE_BUSY , "SQLite database connection is busy") +XPC_MSG_DEF(NS_ERROR_STORAGE_IOERR , "SQLite encountered an IO error") +XPC_MSG_DEF(NS_ERROR_STORAGE_CONSTRAINT , "SQLite database operation failed because a constraint was violated") + +/* plugin related codes (from nsPluginError.h) */ +XPC_MSG_DEF(NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, "Clearing site data by time range not supported by plugin") + +/* character converter related codes (from nsIUnicodeDecoder.h) */ +XPC_MSG_DEF(NS_ERROR_ILLEGAL_INPUT , "The input characters have illegal sequences") + +/* Codes related to signd jars */ +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_NOT_SIGNED , "The JAR is not signed.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY , "An entry in the JAR has been modified after the JAR was signed.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY , "An entry in the JAR has not been signed.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_MISSING , "An entry is missing from the JAR file.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE , "The JAR's signature is wrong.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE , "An entry in the JAR is too large.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_ENTRY_INVALID , "An entry in the JAR is invalid.") +XPC_MSG_DEF(NS_ERROR_SIGNED_JAR_MANIFEST_INVALID , "The JAR's manifest or signature file is invalid.") + +/* Codes related to signed manifests */ +XPC_MSG_DEF(NS_ERROR_SIGNED_APP_MANIFEST_INVALID , "The signed app manifest or signature file is invalid.") + +/* Codes for printing-related errors. */ +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE , "No printers available.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND , "The selected printer could not be found.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE , "Failed to open output file for print to file.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_STARTDOC , "Printing failed while starting the print job.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_ENDDOC , "Printing failed while completing the print job.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_STARTPAGE , "Printing failed while starting a new page.") +XPC_MSG_DEF(NS_ERROR_GFX_PRINTER_DOC_IS_BUSY , "Cannot print this document yet, it is still being loaded.") + +/* Codes related to content */ +XPC_MSG_DEF(NS_ERROR_CONTENT_CRASHED , "The process that hosted this content has crashed.") + +/* Codes for the JS-implemented Push DOM API. These can be removed as part of bug 1252660. */ +XPC_MSG_DEF(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR , "Invalid raw ECDSA P-256 public key.") +XPC_MSG_DEF(NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR , "A subscription with a different application server key already exists.") + +/* Codes defined in WebIDL https://heycam.github.io/webidl/#idl-DOMException-error-names */ +XPC_MSG_DEF(NS_ERROR_DOM_NOT_FOUND_ERR , "The object can not be found here.") +XPC_MSG_DEF(NS_ERROR_DOM_NOT_ALLOWED_ERR , "The request is not allowed.") diff --git a/js/xpconnect/src/xpcObjectHelper.h b/js/xpconnect/src/xpcObjectHelper.h new file mode 100644 index 000000000..10090a901 --- /dev/null +++ b/js/xpconnect/src/xpcObjectHelper.h @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef xpcObjectHelper_h +#define xpcObjectHelper_h + +// Including 'windows.h' will #define GetClassInfo to something else. +#ifdef XP_WIN +#ifdef GetClassInfo +#undef GetClassInfo +#endif +#endif + +#include "mozilla/Attributes.h" +#include +#include "nsCOMPtr.h" +#include "nsIClassInfo.h" +#include "nsISupports.h" +#include "nsIXPCScriptable.h" +#include "nsWrapperCache.h" + +class xpcObjectHelper +{ +public: + explicit xpcObjectHelper(nsISupports* aObject, nsWrapperCache* aCache = nullptr) + : mCanonical(nullptr) + , mObject(aObject) + , mCache(aCache) + { + if (!mCache) { + if (aObject) + CallQueryInterface(aObject, &mCache); + else + mCache = nullptr; + } + } + + nsISupports* Object() + { + return mObject; + } + + nsISupports* GetCanonical() + { + if (!mCanonical) { + mCanonicalStrong = do_QueryInterface(mObject); + mCanonical = mCanonicalStrong; + } + return mCanonical; + } + + already_AddRefed forgetCanonical() + { + MOZ_ASSERT(mCanonical, "Huh, no canonical to forget?"); + + if (!mCanonicalStrong) + mCanonicalStrong = mCanonical; + mCanonical = nullptr; + return mCanonicalStrong.forget(); + } + + nsIClassInfo* GetClassInfo() + { + if (mXPCClassInfo) + return mXPCClassInfo; + if (!mClassInfo) + mClassInfo = do_QueryInterface(mObject); + return mClassInfo; + } + nsXPCClassInfo* GetXPCClassInfo() + { + if (!mXPCClassInfo) { + CallQueryInterface(mObject, getter_AddRefs(mXPCClassInfo)); + } + return mXPCClassInfo; + } + + already_AddRefed forgetXPCClassInfo() + { + GetXPCClassInfo(); + + return mXPCClassInfo.forget(); + } + + // We assert that we can reach an nsIXPCScriptable somehow. + uint32_t GetScriptableFlags() + { + // Try getting an nsXPCClassInfo - this handles DOM scriptable helpers. + nsCOMPtr sinfo = GetXPCClassInfo(); + + // If that didn't work, try just QI-ing. This handles BackstagePass. + if (!sinfo) + sinfo = do_QueryInterface(GetCanonical()); + + // We should have something by now. + MOZ_ASSERT(sinfo); + + // Grab the flags. + return sinfo->GetScriptableFlags(); + } + + nsWrapperCache* GetWrapperCache() + { + return mCache; + } + +protected: + xpcObjectHelper(nsISupports* aObject, nsISupports* aCanonical, + nsWrapperCache* aCache) + : mCanonical(aCanonical) + , mObject(aObject) + , mCache(aCache) + { + if (!mCache && aObject) + CallQueryInterface(aObject, &mCache); + } + + nsCOMPtr mCanonicalStrong; + nsISupports* MOZ_UNSAFE_REF("xpcObjectHelper has been specifically optimized " + "to avoid unnecessary AddRefs and Releases. " + "(see bug 565742)") mCanonical; + +private: + xpcObjectHelper(xpcObjectHelper& aOther) = delete; + + nsISupports* MOZ_UNSAFE_REF("xpcObjectHelper has been specifically optimized " + "to avoid unnecessary AddRefs and Releases. " + "(see bug 565742)") mObject; + nsWrapperCache* mCache; + nsCOMPtr mClassInfo; + RefPtr mXPCClassInfo; +}; + +#endif diff --git a/js/xpconnect/src/xpcprivate.h b/js/xpconnect/src/xpcprivate.h new file mode 100644 index 000000000..d7d5586b8 --- /dev/null +++ b/js/xpconnect/src/xpcprivate.h @@ -0,0 +1,3426 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + * XPConnect allows JS code to manipulate C++ object and C++ code to manipulate + * JS objects. JS manipulation of C++ objects tends to be significantly more + * complex. This comment explains how it is orchestrated by XPConnect. + * + * For each C++ object to be manipulated in JS, there is a corresponding JS + * object. This is called the "flattened JS object". By default, there is an + * additional C++ object involved of type XPCWrappedNative. The XPCWrappedNative + * holds pointers to the C++ object and the flat JS object. + * + * All XPCWrappedNative objects belong to an XPCWrappedNativeScope. These scopes + * are essentially in 1:1 correspondence with JS global objects. The + * XPCWrappedNativeScope has a pointer to the JS global object. The parent of a + * flattened JS object is, by default, the global JS object corresponding to the + * wrapper's XPCWrappedNativeScope (the exception to this rule is when a + * PreCreate hook asks for a different parent; see nsIXPCScriptable below). + * + * Some C++ objects (notably DOM objects) have information associated with them + * that lists the interfaces implemented by these objects. A C++ object exposes + * this information by implementing nsIClassInfo. If a C++ object implements + * nsIClassInfo, then JS code can call its methods without needing to use + * QueryInterface first. Typically, all instances of a C++ class share the same + * nsIClassInfo instance. (That is, obj->QueryInterface(nsIClassInfo) returns + * the same result for every obj of a given class.) + * + * XPConnect tracks nsIClassInfo information in an XPCWrappedNativeProto object. + * A given XPCWrappedNativeScope will have one XPCWrappedNativeProto for each + * nsIClassInfo instance being used. The XPCWrappedNativeProto has an associated + * JS object, which is used as the prototype of all flattened JS objects created + * for C++ objects with the given nsIClassInfo. + * + * Each XPCWrappedNativeProto has a pointer to its XPCWrappedNativeScope. If an + * XPCWrappedNative wraps a C++ object with class info, then it points to its + * XPCWrappedNativeProto. Otherwise it points to its XPCWrappedNativeScope. (The + * pointers are smooshed together in a tagged union.) Either way it can reach + * its scope. + * + * An XPCWrappedNativeProto keeps track of the set of interfaces implemented by + * the C++ object in an XPCNativeSet. (The list of interfaces is obtained by + * calling a method on the nsIClassInfo.) An XPCNativeSet is a collection of + * XPCNativeInterfaces. Each interface stores the list of members, which can be + * methods, constants, getters, or setters. + * + * An XPCWrappedNative also points to an XPCNativeSet. Initially this starts out + * the same as the XPCWrappedNativeProto's set. If there is no proto, it starts + * out as a singleton set containing nsISupports. If JS code QI's new interfaces + * outside of the existing set, the set will grow. All QueryInterface results + * are cached in XPCWrappedNativeTearOff objects, which are linked off of the + * XPCWrappedNative. + * + * Besides having class info, a C++ object may be "scriptable" (i.e., implement + * nsIXPCScriptable). This allows it to implement a more DOM-like interface, + * besides just exposing XPCOM methods and constants. An nsIXPCScriptable + * instance has hooks that correspond to all the normal JSClass hooks. Each + * nsIXPCScriptable instance is mirrored by an XPCNativeScriptableInfo in + * XPConnect. These can have pointers from XPCWrappedNativeProto and + * XPCWrappedNative (since C++ objects can have scriptable info without having + * class info). + */ + +/* All the XPConnect private declarations - only include locally. */ + +#ifndef xpcprivate_h___ +#define xpcprivate_h___ + +#include "mozilla/Alignment.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/GuardObjects.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Preferences.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/dom/ScriptSettings.h" + +#include +#include +#include +#include + +#include "xpcpublic.h" +#include "js/TracingAPI.h" +#include "js/WeakMapPtr.h" +#include "PLDHashTable.h" +#include "nscore.h" +#include "nsXPCOM.h" +#include "nsAutoPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDebug.h" +#include "nsISupports.h" +#include "nsIServiceManager.h" +#include "nsIClassInfoImpl.h" +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsISupportsPrimitives.h" +#include "nsMemory.h" +#include "nsIXPConnect.h" +#include "nsIInterfaceInfo.h" +#include "nsIXPCScriptable.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsXPTCUtils.h" +#include "xptinfo.h" +#include "XPCForwards.h" +#include "XPCLog.h" +#include "xpccomponents.h" +#include "xpcexception.h" +#include "xpcjsid.h" +#include "prenv.h" +#include "prclist.h" +#include "prcvar.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsXPIDLString.h" + +#include "MainThreadUtils.h" + +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsIException.h" + +#include "nsVariant.h" +#include "nsIPropertyBag.h" +#include "nsIProperty.h" +#include "nsCOMArray.h" +#include "nsTArray.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsWrapperCache.h" +#include "nsStringBuffer.h" +#include "nsDataHashtable.h" +#include "nsDeque.h" + +#include "nsIScriptSecurityManager.h" + +#include "nsIPrincipal.h" +#include "nsJSPrincipals.h" +#include "nsIScriptObjectPrincipal.h" +#include "xpcObjectHelper.h" + +#include "SandboxPrivate.h" +#include "BackstagePass.h" +#include "nsAXPCNativeCallContext.h" + +#ifdef XP_WIN +// Nasty MS defines +#ifdef GetClassInfo +#undef GetClassInfo +#endif +#ifdef GetClassName +#undef GetClassName +#endif +#endif /* XP_WIN */ + +/***************************************************************************/ +// default initial sizes for maps (hashtables) + +#define XPC_JS_MAP_LENGTH 32 +#define XPC_JS_CLASS_MAP_LENGTH 32 + +#define XPC_NATIVE_MAP_LENGTH 8 +#define XPC_NATIVE_PROTO_MAP_LENGTH 8 +#define XPC_DYING_NATIVE_PROTO_MAP_LENGTH 8 +#define XPC_NATIVE_INTERFACE_MAP_LENGTH 32 +#define XPC_NATIVE_SET_MAP_LENGTH 32 +#define XPC_THIS_TRANSLATOR_MAP_LENGTH 4 +#define XPC_WRAPPER_MAP_LENGTH 8 + +/***************************************************************************/ +// data declarations... +extern const char XPC_CONTEXT_STACK_CONTRACTID[]; +extern const char XPC_EXCEPTION_CONTRACTID[]; +extern const char XPC_CONSOLE_CONTRACTID[]; +extern const char XPC_SCRIPT_ERROR_CONTRACTID[]; +extern const char XPC_ID_CONTRACTID[]; +extern const char XPC_XPCONNECT_CONTRACTID[]; + +/***************************************************************************/ +// Useful macros... + +#define XPC_STRING_GETTER_BODY(dest, src) \ + NS_ENSURE_ARG_POINTER(dest); \ + char* result; \ + if (src) \ + result = (char*) nsMemory::Clone(src, \ + sizeof(char)*(strlen(src)+1)); \ + else \ + result = nullptr; \ + *dest = result; \ + return (result || !src) ? NS_OK : NS_ERROR_OUT_OF_MEMORY + +// If IS_WN_CLASS for the JSClass of an object is true, the object is a +// wrappednative wrapper, holding the XPCWrappedNative in its private slot. +static inline bool IS_WN_CLASS(const js::Class* clazz) +{ + return clazz->isWrappedNative(); +} + +static inline bool IS_WN_REFLECTOR(JSObject* obj) +{ + return IS_WN_CLASS(js::GetObjectClass(obj)); +} + +/*************************************************************************** +**************************************************************************** +* +* Core runtime and context classes... +* +**************************************************************************** +***************************************************************************/ + +// We have a general rule internally that getters that return addref'd interface +// pointer generally do so using an 'out' parm. When interface pointers are +// returned as function call result values they are not addref'd. Exceptions +// to this rule are noted explicitly. + +class nsXPConnect final : public nsIXPConnect +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCONNECT + + // non-interface implementation +public: + // These get non-addref'd pointers + static nsXPConnect* XPConnect() + { + // Do a release-mode assert that we're not doing anything significant in + // XPConnect off the main thread. If you're an extension developer hitting + // this, you need to change your code. See bug 716167. + if (!MOZ_LIKELY(NS_IsMainThread())) + MOZ_CRASH(); + + return gSelf; + } + + static XPCJSContext* GetContextInstance(); + XPCJSContext* GetContext() {return mContext;} + + static bool IsISupportsDescendant(nsIInterfaceInfo* info); + + static nsIScriptSecurityManager* SecurityManager() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gScriptSecurityManager); + return gScriptSecurityManager; + } + + static nsIPrincipal* SystemPrincipal() + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gSystemPrincipal); + return gSystemPrincipal; + } + + // This returns an AddRef'd pointer. It does not do this with an 'out' param + // only because this form is required by the generic module macro: + // NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR + static nsXPConnect* GetSingleton(); + + // Called by module code in dll startup + static void InitStatics(); + // Called by module code on dll shutdown. + static void ReleaseXPConnectSingleton(); + + bool IsShuttingDown() const {return mShuttingDown;} + + nsresult GetInfoForIID(const nsIID * aIID, nsIInterfaceInfo** info); + nsresult GetInfoForName(const char * name, nsIInterfaceInfo** info); + + virtual nsIPrincipal* GetPrincipal(JSObject* obj, + bool allowShortCircuit) const override; + + void RecordTraversal(void* p, nsISupports* s); + virtual char* DebugPrintJSStack(bool showArgs, + bool showLocals, + bool showThisProps) override; + +protected: + virtual ~nsXPConnect(); + + nsXPConnect(); + +private: + // Singleton instance + static nsXPConnect* gSelf; + static bool gOnceAliveNowDead; + + XPCJSContext* mContext; + bool mShuttingDown; + +public: + static nsIScriptSecurityManager* gScriptSecurityManager; + static nsIPrincipal* gSystemPrincipal; +}; + +/***************************************************************************/ + +class XPCRootSetElem +{ +public: + XPCRootSetElem() + { +#ifdef DEBUG + mNext = nullptr; + mSelfp = nullptr; +#endif + } + + ~XPCRootSetElem() + { + MOZ_ASSERT(!mNext, "Must be unlinked"); + MOZ_ASSERT(!mSelfp, "Must be unlinked"); + } + + inline XPCRootSetElem* GetNextRoot() { return mNext; } + void AddToRootSet(XPCRootSetElem** listHead); + void RemoveFromRootSet(); + +private: + XPCRootSetElem* mNext; + XPCRootSetElem** mSelfp; +}; + +/***************************************************************************/ + +// In the current xpconnect system there can only be one XPCJSContext. +// So, xpconnect can only be used on one JSContext within the process. + +class WatchdogManager; + +enum WatchdogTimestampCategory +{ + TimestampContextStateChange = 0, + TimestampWatchdogWakeup, + TimestampWatchdogHibernateStart, + TimestampWatchdogHibernateStop, + TimestampCount +}; + +class AsyncFreeSnowWhite; + +template +class ShortLivedStringBuffer +{ +public: + StringType* Create() + { + for (uint32_t i = 0; i < ArrayLength(mStrings); ++i) { + if (!mStrings[i]) { + mStrings[i].emplace(); + return mStrings[i].ptr(); + } + } + + // All our internal string wrappers are used, allocate a new string. + return new StringType(); + } + + void Destroy(StringType* string) + { + for (uint32_t i = 0; i < ArrayLength(mStrings); ++i) { + if (mStrings[i] && mStrings[i].ptr() == string) { + // One of our internal strings is no longer in use, mark + // it as such and free its data. + mStrings[i].reset(); + return; + } + } + + // We're done with a string that's not one of our internal + // strings, delete it. + delete string; + } + + ~ShortLivedStringBuffer() + { +#ifdef DEBUG + for (uint32_t i = 0; i < ArrayLength(mStrings); ++i) { + MOZ_ASSERT(!mStrings[i], "Short lived string still in use"); + } +#endif + } + +private: + mozilla::Maybe mStrings[2]; +}; + +class XPCJSContext final : public mozilla::CycleCollectedJSContext +{ +public: + static XPCJSContext* newXPCJSContext(); + static XPCJSContext* Get() { return nsXPConnect::XPConnect()->GetContext(); } + + void RemoveWrappedJS(nsXPCWrappedJS* wrapper); + void AssertInvalidWrappedJSNotInTable(nsXPCWrappedJS* wrapper) const; + + XPCCallContext* GetCallContext() const {return mCallContext;} + XPCCallContext* SetCallContext(XPCCallContext* ccx) + {XPCCallContext* old = mCallContext; mCallContext = ccx; return old;} + + jsid GetResolveName() const {return mResolveName;} + jsid SetResolveName(jsid name) + {jsid old = mResolveName; mResolveName = name; return old;} + + XPCWrappedNative* GetResolvingWrapper() const {return mResolvingWrapper;} + XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w) + {XPCWrappedNative* old = mResolvingWrapper; + mResolvingWrapper = w; return old;} + + JSObject2WrappedJSMap* GetMultiCompartmentWrappedJSMap() const + {return mWrappedJSMap;} + + IID2WrappedJSClassMap* GetWrappedJSClassMap() const + {return mWrappedJSClassMap;} + + IID2NativeInterfaceMap* GetIID2NativeInterfaceMap() const + {return mIID2NativeInterfaceMap;} + + ClassInfo2NativeSetMap* GetClassInfo2NativeSetMap() const + {return mClassInfo2NativeSetMap;} + + NativeSetMap* GetNativeSetMap() const + {return mNativeSetMap;} + + IID2ThisTranslatorMap* GetThisTranslatorMap() const + {return mThisTranslatorMap;} + + XPCWrappedNativeProtoMap* GetDyingWrappedNativeProtoMap() const + {return mDyingWrappedNativeProtoMap;} + + bool JSContextInitialized(JSContext* cx); + + virtual bool + DescribeCustomObjects(JSObject* aObject, const js::Class* aClasp, + char (&aName)[72]) const override; + virtual bool + NoteCustomGCThingXPCOMChildren(const js::Class* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const override; + + virtual void BeforeProcessTask(bool aMightBlock) override; + virtual void AfterProcessTask(uint32_t aNewRecursionDepth) override; + + /** + * Infrastructure for classes that need to defer part of the finalization + * until after the GC has run, for example for objects that we don't want to + * destroy during the GC. + */ + +public: + bool GetDoingFinalization() const {return mDoingFinalization;} + + // Mapping of often used strings to jsid atoms that live 'forever'. + // + // To add a new string: add to this list and to XPCJSContext::mStrings + // at the top of XPCJSContext.cpp + enum { + IDX_CONSTRUCTOR = 0 , + IDX_TO_STRING , + IDX_TO_SOURCE , + IDX_LAST_RESULT , + IDX_RETURN_CODE , + IDX_VALUE , + IDX_QUERY_INTERFACE , + IDX_COMPONENTS , + IDX_WRAPPED_JSOBJECT , + IDX_OBJECT , + IDX_FUNCTION , + IDX_PROTOTYPE , + IDX_CREATE_INSTANCE , + IDX_ITEM , + IDX_PROTO , + IDX_ITERATOR , + IDX_EXPOSEDPROPS , + IDX_EVAL , + IDX_CONTROLLERS , + IDX_REALFRAMEELEMENT , + IDX_LENGTH , + IDX_NAME , + IDX_UNDEFINED , + IDX_EMPTYSTRING , + IDX_FILENAME , + IDX_LINENUMBER , + IDX_COLUMNNUMBER , + IDX_STACK , + IDX_MESSAGE , + IDX_LASTINDEX , + IDX_TOTAL_COUNT // just a count of the above + }; + + JS::HandleId GetStringID(unsigned index) const + { + MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range"); + // fromMarkedLocation() is safe because the string is interned. + return JS::HandleId::fromMarkedLocation(&mStrIDs[index]); + } + JS::HandleValue GetStringJSVal(unsigned index) const + { + MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range"); + // fromMarkedLocation() is safe because the string is interned. + return JS::HandleValue::fromMarkedLocation(&mStrJSVals[index]); + } + const char* GetStringName(unsigned index) const + { + MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range"); + return mStrings[index]; + } + + virtual bool UsefulToMergeZones() const override; + void TraceNativeBlackRoots(JSTracer* trc) override; + void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) override; + void TraverseAdditionalNativeRoots(nsCycleCollectionNoteRootCallback& cb) override; + void UnmarkSkippableJSHolders(); + void PrepareForForgetSkippable() override; + void BeginCycleCollectionCallback() override; + void EndCycleCollectionCallback(mozilla::CycleCollectorResults& aResults) override; + void DispatchDeferredDeletion(bool aContinuation, bool aPurge = false) override; + + void CustomGCCallback(JSGCStatus status) override; + void CustomOutOfMemoryCallback() override; + void CustomLargeAllocationFailureCallback() override; + static void GCSliceCallback(JSContext* cx, + JS::GCProgress progress, + const JS::GCDescription& desc); + static void DoCycleCollectionCallback(JSContext* cx); + static void FinalizeCallback(JSFreeOp* fop, + JSFinalizeStatus status, + bool isZoneGC, + void* data); + static void WeakPointerZoneGroupCallback(JSContext* cx, void* data); + static void WeakPointerCompartmentCallback(JSContext* cx, JSCompartment* comp, void* data); + + inline void AddVariantRoot(XPCTraceableVariant* variant); + inline void AddWrappedJSRoot(nsXPCWrappedJS* wrappedJS); + inline void AddObjectHolderRoot(XPCJSObjectHolder* holder); + + void DebugDump(int16_t depth); + + bool GCIsRunning() const {return mGCIsRunning;} + + ~XPCJSContext(); + + ShortLivedStringBuffer mScratchStrings; + ShortLivedStringBuffer mScratchCStrings; + + void AddGCCallback(xpcGCCallback cb); + void RemoveGCCallback(xpcGCCallback cb); + + static void ActivityCallback(void* arg, bool active); + static bool InterruptCallback(JSContext* cx); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + AutoMarkingPtr** GetAutoRootsAdr() {return &mAutoRoots;} + + JSObject* UnprivilegedJunkScope() { return mUnprivilegedJunkScope; } + JSObject* PrivilegedJunkScope() { return mPrivilegedJunkScope; } + JSObject* CompilationScope() { return mCompilationScope; } + + void InitSingletonScopes(); + void DeleteSingletonScopes(); + + PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory); + + nsresult GetPendingResult() { return mPendingResult; } + void SetPendingResult(nsresult rv) { mPendingResult = rv; } + +private: + XPCJSContext(); + + MOZ_IS_CLASS_INIT + nsresult Initialize(); + + void ReleaseIncrementally(nsTArray& array); + + static const char* const mStrings[IDX_TOTAL_COUNT]; + jsid mStrIDs[IDX_TOTAL_COUNT]; + JS::Value mStrJSVals[IDX_TOTAL_COUNT]; + + XPCCallContext* mCallContext; + AutoMarkingPtr* mAutoRoots; + jsid mResolveName; + XPCWrappedNative* mResolvingWrapper; + JSObject2WrappedJSMap* mWrappedJSMap; + IID2WrappedJSClassMap* mWrappedJSClassMap; + IID2NativeInterfaceMap* mIID2NativeInterfaceMap; + ClassInfo2NativeSetMap* mClassInfo2NativeSetMap; + NativeSetMap* mNativeSetMap; + IID2ThisTranslatorMap* mThisTranslatorMap; + XPCWrappedNativeProtoMap* mDyingWrappedNativeProtoMap; + bool mGCIsRunning; + nsTArray mNativesToReleaseArray; + bool mDoingFinalization; + XPCRootSetElem* mVariantRoots; + XPCRootSetElem* mWrappedJSRoots; + XPCRootSetElem* mObjectHolderRoots; + nsTArray extraGCCallbacks; + RefPtr mWatchdogManager; + JS::GCSliceCallback mPrevGCSliceCallback; + JS::DoCycleCollectionCallback mPrevDoCycleCollectionCallback; + JS::PersistentRootedObject mUnprivilegedJunkScope; + JS::PersistentRootedObject mPrivilegedJunkScope; + JS::PersistentRootedObject mCompilationScope; + RefPtr mAsyncSnowWhiteFreer; + + // If we spend too much time running JS code in an event handler, then we + // want to show the slow script UI. The timeout T is controlled by prefs. We + // invoke the interrupt callback once after T/2 seconds and set + // mSlowScriptSecondHalf to true. After another T/2 seconds, we invoke the + // interrupt callback again. Since mSlowScriptSecondHalf is now true, it + // shows the slow script UI. The reason we invoke the callback twice is to + // ensure that putting the computer to sleep while running a script doesn't + // cause the UI to be shown. If the laptop goes to sleep during one of the + // timeout periods, the script still has the other T/2 seconds to complete + // before the slow script UI is shown. + bool mSlowScriptSecondHalf; + + // mSlowScriptCheckpoint is set to the time when: + // 1. We started processing the current event, or + // 2. mSlowScriptSecondHalf was set to true + // (whichever comes later). We use it to determine whether the interrupt + // callback needs to do anything. + mozilla::TimeStamp mSlowScriptCheckpoint; + // Accumulates total time we actually waited for telemetry + mozilla::TimeDuration mSlowScriptActualWait; + bool mTimeoutAccumulated; + + // mPendingResult is used to implement Components.returnCode. Only really + // meaningful while calling through XPCWrappedJS. + nsresult mPendingResult; + + friend class Watchdog; + friend class AutoLockWatchdog; + friend class XPCIncrementalReleaseRunnable; +}; + +/***************************************************************************/ + +// No virtuals +// XPCCallContext is ALWAYS declared as a local variable in some function; +// i.e. instance lifetime is always controled by some C++ function returning. +// +// These things are created frequently in many places. We *intentionally* do +// not inialialize all members in order to save on construction overhead. +// Some constructor pass more valid params than others. We init what must be +// init'd and leave other members undefined. In debug builds the accessors +// use a CHECK_STATE macro to track whether or not the object is in a valid +// state to answer the question a caller might be asking. As long as this +// class is maintained correctly it can do its job without a bunch of added +// overhead from useless initializations and non-DEBUG error checking. +// +// Note that most accessors are inlined. + +class MOZ_STACK_CLASS XPCCallContext final : public nsAXPCNativeCallContext +{ +public: + NS_IMETHOD GetCallee(nsISupports** aResult); + NS_IMETHOD GetCalleeMethodIndex(uint16_t* aResult); + NS_IMETHOD GetJSContext(JSContext** aResult); + NS_IMETHOD GetArgc(uint32_t* aResult); + NS_IMETHOD GetArgvPtr(JS::Value** aResult); + NS_IMETHOD GetCalleeInterface(nsIInterfaceInfo** aResult); + NS_IMETHOD GetCalleeClassInfo(nsIClassInfo** aResult); + NS_IMETHOD GetPreviousCallContext(nsAXPCNativeCallContext** aResult); + + enum {NO_ARGS = (unsigned) -1}; + + explicit XPCCallContext(JSContext* cx, + JS::HandleObject obj = nullptr, + JS::HandleObject funobj = nullptr, + JS::HandleId id = JSID_VOIDHANDLE, + unsigned argc = NO_ARGS, + JS::Value* argv = nullptr, + JS::Value* rval = nullptr); + + virtual ~XPCCallContext(); + + inline bool IsValid() const ; + + inline XPCJSContext* GetContext() const ; + inline JSContext* GetJSContext() const ; + inline bool GetContextPopRequired() const ; + inline XPCCallContext* GetPrevCallContext() const ; + + inline JSObject* GetFlattenedJSObject() const ; + inline nsISupports* GetIdentityObject() const ; + inline XPCWrappedNative* GetWrapper() const ; + inline XPCWrappedNativeProto* GetProto() const ; + + inline bool CanGetTearOff() const ; + inline XPCWrappedNativeTearOff* GetTearOff() const ; + + inline XPCNativeScriptableInfo* GetScriptableInfo() const ; + inline bool CanGetSet() const ; + inline XPCNativeSet* GetSet() const ; + inline bool CanGetInterface() const ; + inline XPCNativeInterface* GetInterface() const ; + inline XPCNativeMember* GetMember() const ; + inline bool HasInterfaceAndMember() const ; + inline jsid GetName() const ; + inline bool GetStaticMemberIsLocal() const ; + inline unsigned GetArgc() const ; + inline JS::Value* GetArgv() const ; + inline JS::Value* GetRetVal() const ; + + inline uint16_t GetMethodIndex() const ; + inline void SetMethodIndex(uint16_t index) ; + + inline jsid GetResolveName() const; + inline jsid SetResolveName(JS::HandleId name); + + inline XPCWrappedNative* GetResolvingWrapper() const; + inline XPCWrappedNative* SetResolvingWrapper(XPCWrappedNative* w); + + inline void SetRetVal(const JS::Value& val); + + void SetName(jsid name); + void SetArgsAndResultPtr(unsigned argc, JS::Value* argv, JS::Value* rval); + void SetCallInfo(XPCNativeInterface* iface, XPCNativeMember* member, + bool isSetter); + + nsresult CanCallNow(); + + void SystemIsBeingShutDown(); + + operator JSContext*() const {return GetJSContext();} + +private: + + // no copy ctor or assignment allowed + XPCCallContext(const XPCCallContext& r) = delete; + XPCCallContext& operator= (const XPCCallContext& r) = delete; + +private: + // posible values for mState + enum State { + INIT_FAILED, + SYSTEM_SHUTDOWN, + HAVE_CONTEXT, + HAVE_OBJECT, + HAVE_NAME, + HAVE_ARGS, + READY_TO_CALL, + CALL_DONE + }; + +#ifdef DEBUG +inline void CHECK_STATE(int s) const {MOZ_ASSERT(mState >= s, "bad state");} +#else +#define CHECK_STATE(s) ((void)0) +#endif + +private: + JSAutoRequest mAr; + State mState; + + RefPtr mXPC; + + XPCJSContext* mXPCJSContext; + JSContext* mJSContext; + + // ctor does not necessarily init the following. BEWARE! + + XPCCallContext* mPrevCallContext; + + XPCWrappedNative* mWrapper; + XPCWrappedNativeTearOff* mTearOff; + + XPCNativeScriptableInfo* mScriptableInfo; + + RefPtr mSet; + RefPtr mInterface; + XPCNativeMember* mMember; + + JS::RootedId mName; + bool mStaticMemberIsLocal; + + unsigned mArgc; + JS::Value* mArgv; + JS::Value* mRetVal; + + uint16_t mMethodIndex; +}; + +/*************************************************************************** +**************************************************************************** +* +* Core classes for wrapped native objects for use from JavaScript... +* +**************************************************************************** +***************************************************************************/ + +// These are the various JSClasses and callbacks whose use that required +// visibility from more than one .cpp file. + +extern const js::Class XPC_WN_NoHelper_JSClass; +extern const js::Class XPC_WN_NoMods_Proto_JSClass; +extern const js::Class XPC_WN_ModsAllowed_Proto_JSClass; +extern const js::Class XPC_WN_Tearoff_JSClass; +#define XPC_WN_TEAROFF_RESERVED_SLOTS 1 +#define XPC_WN_TEAROFF_FLAT_OBJECT_SLOT 0 +extern const js::Class XPC_WN_NoHelper_Proto_JSClass; + +extern bool +XPC_WN_CallMethod(JSContext* cx, unsigned argc, JS::Value* vp); + +extern bool +XPC_WN_GetterSetter(JSContext* cx, unsigned argc, JS::Value* vp); + +// Maybe this macro should check for class->enumerate == +// XPC_WN_Shared_Proto_Enumerate or something rather than checking for +// 4 classes? +static inline bool IS_PROTO_CLASS(const js::Class* clazz) +{ + return clazz == &XPC_WN_NoMods_Proto_JSClass || + clazz == &XPC_WN_ModsAllowed_Proto_JSClass; +} + +typedef js::HashSet, + js::SystemAllocPolicy> InterpositionWhitelist; + +struct InterpositionWhitelistPair { + nsIAddonInterposition* interposition; + InterpositionWhitelist whitelist; +}; + +typedef nsTArray InterpositionWhitelistArray; + +/***************************************************************************/ +// XPCWrappedNativeScope is one-to-one with a JS global object. + +class nsIAddonInterposition; +class nsXPCComponentsBase; +class XPCWrappedNativeScope final : public PRCList +{ +public: + + XPCJSContext* + GetContext() const {return XPCJSContext::Get();} + + Native2WrappedNativeMap* + GetWrappedNativeMap() const {return mWrappedNativeMap;} + + ClassInfo2WrappedNativeProtoMap* + GetWrappedNativeProtoMap() const {return mWrappedNativeProtoMap;} + + nsXPCComponentsBase* + GetComponents() const {return mComponents;} + + // Forces the creation of a privileged |Components| object, even in + // content scopes. This will crash if used outside of automation. + void + ForcePrivilegedComponents(); + + bool AttachComponentsObject(JSContext* aCx); + + // Returns the JS object reflection of the Components object. + bool + GetComponentsJSObject(JS::MutableHandleObject obj); + + JSObject* + GetGlobalJSObject() const { + return mGlobalJSObject; + } + + JSObject* + GetGlobalJSObjectPreserveColor() const {return mGlobalJSObject.unbarrieredGet();} + + nsIPrincipal* + GetPrincipal() const { + JSCompartment* c = js::GetObjectCompartment(mGlobalJSObject); + return nsJSPrincipals::get(JS_GetCompartmentPrincipals(c)); + } + + JSObject* + GetExpandoChain(JS::HandleObject target); + + bool + SetExpandoChain(JSContext* cx, JS::HandleObject target, JS::HandleObject chain); + + static void + SystemIsBeingShutDown(); + + static void + TraceWrappedNativesInAllScopes(JSTracer* trc, XPCJSContext* cx); + + void TraceSelf(JSTracer* trc) { + MOZ_ASSERT(mGlobalJSObject); + mGlobalJSObject.trace(trc, "XPCWrappedNativeScope::mGlobalJSObject"); + } + + void TraceInside(JSTracer* trc) { + if (mContentXBLScope) + mContentXBLScope.trace(trc, "XPCWrappedNativeScope::mXBLScope"); + for (size_t i = 0; i < mAddonScopes.Length(); i++) + mAddonScopes[i].trace(trc, "XPCWrappedNativeScope::mAddonScopes"); + if (mXrayExpandos.initialized()) + mXrayExpandos.trace(trc); + } + + static void + SuspectAllWrappers(XPCJSContext* cx, nsCycleCollectionNoteRootCallback& cb); + + static void + SweepAllWrappedNativeTearOffs(); + + static void + UpdateWeakPointersAfterGC(XPCJSContext* cx); + + static void + KillDyingScopes(); + + static void + DebugDumpAllScopes(int16_t depth); + + void + DebugDump(int16_t depth); + + struct ScopeSizeInfo { + explicit ScopeSizeInfo(mozilla::MallocSizeOf mallocSizeOf) + : mMallocSizeOf(mallocSizeOf), + mScopeAndMapSize(0), + mProtoAndIfaceCacheSize(0) + {} + + mozilla::MallocSizeOf mMallocSizeOf; + size_t mScopeAndMapSize; + size_t mProtoAndIfaceCacheSize; + }; + + static void + AddSizeOfAllScopesIncludingThis(ScopeSizeInfo* scopeSizeInfo); + + void + AddSizeOfIncludingThis(ScopeSizeInfo* scopeSizeInfo); + + bool + IsValid() const {return mContext != nullptr;} + + static bool + IsDyingScope(XPCWrappedNativeScope* scope); + + typedef js::HashSet, + js::MovableCellHasher>, + js::SystemAllocPolicy> DOMExpandoSet; + + bool RegisterDOMExpandoObject(JSObject* expando) { + // Expandos are proxy objects, and proxies are always tenured. + JS::AssertGCThingMustBeTenured(expando); + if (!mDOMExpandoSet) { + mDOMExpandoSet = new DOMExpandoSet(); + if (!mDOMExpandoSet->init(8)) + return false; + } + return mDOMExpandoSet->put(JS::Heap(expando)); + } + void RemoveDOMExpandoObject(JSObject* expando) { + if (mDOMExpandoSet) { + DOMExpandoSet::Ptr p = mDOMExpandoSet->lookup(JS::Heap(expando)); + MOZ_ASSERT(p.found()); + mDOMExpandoSet->remove(p); + } + } + + typedef js::HashMap, + js::PointerHasher, + js::SystemAllocPolicy> InterpositionMap; + + typedef js::HashSet, + js::SystemAllocPolicy> AddonSet; + + // Gets the appropriate scope object for XBL in this scope. The context + // must be same-compartment with the global upon entering, and the scope + // object is wrapped into the compartment of the global. + JSObject* EnsureContentXBLScope(JSContext* cx); + + JSObject* EnsureAddonScope(JSContext* cx, JSAddonId* addonId); + + XPCWrappedNativeScope(JSContext* cx, JS::HandleObject aGlobal); + + nsAutoPtr mWaiverWrapperMap; + + bool IsContentXBLScope() { return mIsContentXBLScope; } + bool AllowContentXBLScope(); + bool UseContentXBLScope() { return mUseContentXBLScope; } + void ClearContentXBLScope() { mContentXBLScope = nullptr; } + + bool IsAddonScope() { return mIsAddonScope; } + + bool HasInterposition() { return mInterposition; } + nsCOMPtr GetInterposition(); + + static bool SetAddonInterposition(JSContext* cx, + JSAddonId* addonId, + nsIAddonInterposition* interp); + + static InterpositionWhitelist* GetInterpositionWhitelist(nsIAddonInterposition* interposition); + static bool UpdateInterpositionWhitelist(JSContext* cx, + nsIAddonInterposition* interposition); + + void SetAddonCallInterposition() { mHasCallInterpositions = true; } + bool HasCallInterposition() { return mHasCallInterpositions; }; + + static bool AllowCPOWsInAddon(JSContext* cx, JSAddonId* addonId, bool allow); + +protected: + virtual ~XPCWrappedNativeScope(); + + XPCWrappedNativeScope() = delete; + +private: + class ClearInterpositionsObserver final : public nsIObserver { + ~ClearInterpositionsObserver() {} + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + }; + + static XPCWrappedNativeScope* gScopes; + static XPCWrappedNativeScope* gDyingScopes; + + static bool gShutdownObserverInitialized; + static InterpositionMap* gInterpositionMap; + static AddonSet* gAllowCPOWAddonSet; + + static InterpositionWhitelistArray* gInterpositionWhitelists; + + XPCJSContext* mContext; + Native2WrappedNativeMap* mWrappedNativeMap; + ClassInfo2WrappedNativeProtoMap* mWrappedNativeProtoMap; + RefPtr mComponents; + XPCWrappedNativeScope* mNext; + // The JS global object for this scope. If non-null, this will be the + // default parent for the XPCWrappedNatives that have us as the scope, + // unless a PreCreate hook overrides it. Note that this _may_ be null (see + // constructor). + JS::ObjectPtr mGlobalJSObject; + + // XBL Scope. This is is a lazily-created sandbox for non-system scopes. + // EnsureContentXBLScope() decides whether it needs to be created or not. + // This reference is wrapped into the compartment of mGlobalJSObject. + JS::ObjectPtr mContentXBLScope; + + // Lazily created sandboxes for addon code. + nsTArray mAddonScopes; + + // This is a service that will be use to interpose on some property accesses on + // objects from other scope, for add-on compatibility reasons. + nsCOMPtr mInterposition; + + // If this flag is set, we intercept function calls on vanilla JS function objects + // from this scope if the caller scope has mInterposition set. + bool mHasCallInterpositions; + + nsAutoPtr mDOMExpandoSet; + + JS::WeakMapPtr mXrayExpandos; + + bool mIsContentXBLScope; + bool mIsAddonScope; + + // For remote XUL domains, we run all XBL in the content scope for compat + // reasons (though we sometimes pref this off for automation). We separately + // track the result of this decision (mAllowContentXBLScope), from the decision + // of whether to actually _use_ an XBL scope (mUseContentXBLScope), which depends + // on the type of global and whether the compartment is system principal + // or not. + // + // This distinction is useful primarily because, if true, we know that we + // have no way of distinguishing XBL script from content script for the + // given scope. In these (unsupported) situations, we just always claim to + // be XBL. + bool mAllowContentXBLScope; + bool mUseContentXBLScope; +}; + +/***************************************************************************/ +// Slots we use for our functions +#define XPC_FUNCTION_NATIVE_MEMBER_SLOT 0 +#define XPC_FUNCTION_PARENT_OBJECT_SLOT 1 + +/***************************************************************************/ +// XPCNativeMember represents a single idl declared method, attribute or +// constant. + +// Tight. No virtual methods. Can be bitwise copied (until any resolution done). + +class XPCNativeMember final +{ +public: + static bool GetCallInfo(JSObject* funobj, + RefPtr* pInterface, + XPCNativeMember** pMember); + + jsid GetName() const {return mName;} + + uint16_t GetIndex() const {return mIndex;} + + bool GetConstantValue(XPCCallContext& ccx, XPCNativeInterface* iface, + JS::Value* pval) + {MOZ_ASSERT(IsConstant(), + "Only call this if you're sure this is a constant!"); + return Resolve(ccx, iface, nullptr, pval);} + + bool NewFunctionObject(XPCCallContext& ccx, XPCNativeInterface* iface, + JS::HandleObject parent, JS::Value* pval); + + bool IsMethod() const + {return 0 != (mFlags & METHOD);} + + bool IsConstant() const + {return 0 != (mFlags & CONSTANT);} + + bool IsAttribute() const + {return 0 != (mFlags & GETTER);} + + bool IsWritableAttribute() const + {return 0 != (mFlags & SETTER_TOO);} + + bool IsReadOnlyAttribute() const + {return IsAttribute() && !IsWritableAttribute();} + + + void SetName(jsid a) {mName = a;} + + void SetMethod(uint16_t index) + {mFlags = METHOD; mIndex = index;} + + void SetConstant(uint16_t index) + {mFlags = CONSTANT; mIndex = index;} + + void SetReadOnlyAttribute(uint16_t index) + {mFlags = GETTER; mIndex = index;} + + void SetWritableAttribute() + {MOZ_ASSERT(mFlags == GETTER,"bad"); mFlags = GETTER | SETTER_TOO;} + + static uint16_t GetMaxIndexInInterface() + {return (1<<12) - 1;} + + inline XPCNativeInterface* GetInterface() const; + + void SetIndexInInterface(uint16_t index) + {mIndexInInterface = index;} + + /* default ctor - leave random contents */ + XPCNativeMember() {MOZ_COUNT_CTOR(XPCNativeMember);} + ~XPCNativeMember() {MOZ_COUNT_DTOR(XPCNativeMember);} + +private: + bool Resolve(XPCCallContext& ccx, XPCNativeInterface* iface, + JS::HandleObject parent, JS::Value* vp); + + enum { + METHOD = 0x01, + CONSTANT = 0x02, + GETTER = 0x04, + SETTER_TOO = 0x08 + // If you add a flag here, you may need to make mFlags wider and either + // make mIndexInInterface narrower (and adjust + // XPCNativeInterface::NewInstance accordingly) or make this object + // bigger. + }; + +private: + // our only data... + jsid mName; + uint16_t mIndex; + // mFlags needs to be wide enogh to hold the flags in the above enum. + uint16_t mFlags : 4; + // mIndexInInterface is the index of this in our XPCNativeInterface's + // mMembers. In theory our XPCNativeInterface could have as many as 2^15-1 + // members (since mMemberCount is 15-bit) but in practice we prevent + // creation of XPCNativeInterfaces which have more than 2^12 members. + // If the width of this field changes, update GetMaxIndexInInterface. + uint16_t mIndexInInterface : 12; +} JS_HAZ_NON_GC_POINTER; // Only stores a pinned string + +/***************************************************************************/ +// XPCNativeInterface represents a single idl declared interface. This is +// primarily the set of XPCNativeMembers. + +// Tight. No virtual methods. + +class XPCNativeInterface final +{ + public: + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeInterface, + DestroyInstance(this)) + + static already_AddRefed GetNewOrUsed(const nsIID* iid); + static already_AddRefed GetNewOrUsed(nsIInterfaceInfo* info); + static already_AddRefed GetNewOrUsed(const char* name); + static already_AddRefed GetISupports(); + + inline nsIInterfaceInfo* GetInterfaceInfo() const {return mInfo.get();} + inline jsid GetName() const {return mName;} + + inline const nsIID* GetIID() const; + inline const char* GetNameString() const; + inline XPCNativeMember* FindMember(jsid name) const; + + inline bool HasAncestor(const nsIID* iid) const; + static inline size_t OffsetOfMembers(); + + uint16_t GetMemberCount() const { + return mMemberCount; + } + XPCNativeMember* GetMemberAt(uint16_t i) { + MOZ_ASSERT(i < mMemberCount, "bad index"); + return &mMembers[i]; + } + + void DebugDump(int16_t depth); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + protected: + static already_AddRefed NewInstance(nsIInterfaceInfo* aInfo); + + XPCNativeInterface() = delete; + XPCNativeInterface(nsIInterfaceInfo* aInfo, jsid aName) + : mInfo(aInfo), mName(aName), mMemberCount(0) + {} + ~XPCNativeInterface(); + + void* operator new(size_t, void* p) CPP_THROW_NEW {return p;} + + XPCNativeInterface(const XPCNativeInterface& r) = delete; + XPCNativeInterface& operator= (const XPCNativeInterface& r) = delete; + + static void DestroyInstance(XPCNativeInterface* inst); + +private: + nsCOMPtr mInfo; + jsid mName; + uint16_t mMemberCount; + XPCNativeMember mMembers[1]; // always last - object sized for array +}; + +/***************************************************************************/ +// XPCNativeSetKey is used to key a XPCNativeSet in a NativeSetMap. +// It represents a new XPCNativeSet we are considering constructing, without +// requiring that the set actually be built. + +class MOZ_STACK_CLASS XPCNativeSetKey final +{ +public: + // This represents an existing set |baseSet|. + explicit XPCNativeSetKey(XPCNativeSet* baseSet) + : mBaseSet(baseSet), mAddition(nullptr) + { + MOZ_ASSERT(baseSet); + } + + // This represents a new set containing only nsISupports and + // |addition|. + explicit XPCNativeSetKey(XPCNativeInterface* addition) + : mBaseSet(nullptr), mAddition(addition) + { + MOZ_ASSERT(addition); + } + + // This represents the existing set |baseSet| with the interface + // |addition| inserted after existing interfaces. |addition| must + // not already be present in |baseSet|. + explicit XPCNativeSetKey(XPCNativeSet* baseSet, + XPCNativeInterface* addition); + ~XPCNativeSetKey() {} + + XPCNativeSet* GetBaseSet() const {return mBaseSet;} + XPCNativeInterface* GetAddition() const {return mAddition;} + + PLDHashNumber Hash() const; + + // Allow shallow copy + +private: + RefPtr mBaseSet; + RefPtr mAddition; +}; + +/***************************************************************************/ +// XPCNativeSet represents an ordered collection of XPCNativeInterface pointers. + +class XPCNativeSet final +{ + public: + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(XPCNativeSet, + DestroyInstance(this)) + + static already_AddRefed GetNewOrUsed(const nsIID* iid); + static already_AddRefed GetNewOrUsed(nsIClassInfo* classInfo); + static already_AddRefed GetNewOrUsed(XPCNativeSetKey* key); + + // This generates a union set. + // + // If preserveFirstSetOrder is true, the elements from |firstSet| come first, + // followed by any non-duplicate items from |secondSet|. If false, the same + // algorithm is applied; but if we detect that |secondSet| is a superset of + // |firstSet|, we return |secondSet| without worrying about whether the + // ordering might differ from |firstSet|. + static already_AddRefed GetNewOrUsed(XPCNativeSet* firstSet, + XPCNativeSet* secondSet, + bool preserveFirstSetOrder); + + static void ClearCacheEntryForClassInfo(nsIClassInfo* classInfo); + + inline bool FindMember(jsid name, XPCNativeMember** pMember, + uint16_t* pInterfaceIndex) const; + + inline bool FindMember(jsid name, XPCNativeMember** pMember, + RefPtr* pInterface) const; + + inline bool FindMember(JS::HandleId name, + XPCNativeMember** pMember, + RefPtr* pInterface, + XPCNativeSet* protoSet, + bool* pIsLocal) const; + + inline bool HasInterface(XPCNativeInterface* aInterface) const; + inline bool HasInterfaceWithAncestor(XPCNativeInterface* aInterface) const; + inline bool HasInterfaceWithAncestor(const nsIID* iid) const; + + inline XPCNativeInterface* FindInterfaceWithIID(const nsIID& iid) const; + + inline XPCNativeInterface* FindNamedInterface(jsid name) const; + + uint16_t GetMemberCount() const { + return mMemberCount; + } + uint16_t GetInterfaceCount() const { + return mInterfaceCount; + } + XPCNativeInterface** GetInterfaceArray() { + return mInterfaces; + } + + XPCNativeInterface* GetInterfaceAt(uint16_t i) + {MOZ_ASSERT(i < mInterfaceCount, "bad index"); return mInterfaces[i];} + + inline bool MatchesSetUpToInterface(const XPCNativeSet* other, + XPCNativeInterface* iface) const; + + void DebugDump(int16_t depth); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + + protected: + static already_AddRefed NewInstance(nsTArray>&& array); + static already_AddRefed NewInstanceMutate(XPCNativeSetKey* key); + + XPCNativeSet() + : mMemberCount(0), mInterfaceCount(0) + {} + ~XPCNativeSet(); + void* operator new(size_t, void* p) CPP_THROW_NEW {return p;} + + static void DestroyInstance(XPCNativeSet* inst); + + private: + uint16_t mMemberCount; + uint16_t mInterfaceCount; + // Always last - object sized for array. + // These are strong references. + XPCNativeInterface* mInterfaces[1]; +}; + +/***************************************************************************/ +// XPCNativeScriptableFlags is a wrapper class that holds the flags returned +// from calls to nsIXPCScriptable::GetScriptableFlags(). It has convenience +// methods to check for particular bitflags. + +class XPCNativeScriptableFlags final +{ +public: + explicit XPCNativeScriptableFlags(uint32_t flags = 0) : mFlags(flags) {} + + uint32_t GetFlags() const { return mFlags; } + void SetFlags(uint32_t flags) { mFlags = flags; } + + operator uint32_t() const { return GetFlags(); } + + XPCNativeScriptableFlags(const XPCNativeScriptableFlags& r) + { + mFlags = r.GetFlags(); + } + + XPCNativeScriptableFlags& operator= (const XPCNativeScriptableFlags& r) + { + mFlags = r.GetFlags(); + return *this; + } + +#ifdef GET_IT +#undef GET_IT +#endif +#define GET_IT(f_) const { return 0 != (mFlags & nsIXPCScriptable:: f_ ); } + + bool WantPreCreate() GET_IT(WANT_PRECREATE) + bool WantAddProperty() GET_IT(WANT_ADDPROPERTY) + bool WantGetProperty() GET_IT(WANT_GETPROPERTY) + bool WantSetProperty() GET_IT(WANT_SETPROPERTY) + bool WantEnumerate() GET_IT(WANT_ENUMERATE) + bool WantNewEnumerate() GET_IT(WANT_NEWENUMERATE) + bool WantResolve() GET_IT(WANT_RESOLVE) + bool WantFinalize() GET_IT(WANT_FINALIZE) + bool WantCall() GET_IT(WANT_CALL) + bool WantConstruct() GET_IT(WANT_CONSTRUCT) + bool WantHasInstance() GET_IT(WANT_HASINSTANCE) + bool UseJSStubForAddProperty() GET_IT(USE_JSSTUB_FOR_ADDPROPERTY) + bool UseJSStubForDelProperty() GET_IT(USE_JSSTUB_FOR_DELPROPERTY) + bool UseJSStubForSetProperty() GET_IT(USE_JSSTUB_FOR_SETPROPERTY) + bool DontEnumQueryInterface() GET_IT(DONT_ENUM_QUERY_INTERFACE) + bool DontAskInstanceForScriptable() GET_IT(DONT_ASK_INSTANCE_FOR_SCRIPTABLE) + bool ClassInfoInterfacesOnly() GET_IT(CLASSINFO_INTERFACES_ONLY) + bool AllowPropModsDuringResolve() GET_IT(ALLOW_PROP_MODS_DURING_RESOLVE) + bool AllowPropModsToPrototype() GET_IT(ALLOW_PROP_MODS_TO_PROTOTYPE) + bool IsGlobalObject() GET_IT(IS_GLOBAL_OBJECT) + bool DontReflectInterfaceNames() GET_IT(DONT_REFLECT_INTERFACE_NAMES) + +#undef GET_IT + +private: + uint32_t mFlags; +}; + +/***************************************************************************/ +// XPCNativeScriptableInfo is a trivial wrapper for nsIXPCScriptable which +// should be removed eventually. + +class XPCNativeScriptableInfo final +{ +public: + static XPCNativeScriptableInfo* + Construct(const XPCNativeScriptableCreateInfo* sci); + + nsIXPCScriptable* + GetCallback() const { return mCallback; } + + XPCNativeScriptableFlags + GetFlags() const { return XPCNativeScriptableFlags(mCallback->GetScriptableFlags()); } + + const JSClass* + GetJSClass() { return Jsvalify(mCallback->GetClass()); } + +protected: + explicit XPCNativeScriptableInfo(nsIXPCScriptable* aCallback) + : mCallback(aCallback) + { + MOZ_COUNT_CTOR(XPCNativeScriptableInfo); + } +public: + ~XPCNativeScriptableInfo() + { + MOZ_COUNT_DTOR(XPCNativeScriptableInfo); + } +private: + + // disable copy ctor and assignment + XPCNativeScriptableInfo(const XPCNativeScriptableInfo& r) = delete; + XPCNativeScriptableInfo& operator= (const XPCNativeScriptableInfo& r) = delete; + +private: + nsCOMPtr mCallback; +}; + +/***************************************************************************/ +// XPCNativeScriptableCreateInfo is used in creating new wrapper and protos. +// it abstracts out the scriptable interface pointer and the flags. After +// creation these are factored differently using XPCNativeScriptableInfo. + +class MOZ_STACK_CLASS XPCNativeScriptableCreateInfo final +{ +public: + + explicit XPCNativeScriptableCreateInfo(const XPCNativeScriptableInfo& si) + : mCallback(si.GetCallback()), mFlags(si.GetFlags()) {} + + XPCNativeScriptableCreateInfo(already_AddRefed&& callback, + XPCNativeScriptableFlags flags) + : mCallback(callback), mFlags(flags) {} + + XPCNativeScriptableCreateInfo() + : mFlags(0) {} + + + nsIXPCScriptable* + GetCallback() const {return mCallback;} + + const XPCNativeScriptableFlags& + GetFlags() const {return mFlags;} + + void + SetCallback(already_AddRefed&& callback) + {mCallback = callback;} + + void + SetFlags(const XPCNativeScriptableFlags& flags) {mFlags = flags;} + +private: + // XXX: the flags are the same as the ones gettable from the callback. This + // redundancy should be removed eventually. + nsCOMPtr mCallback; + XPCNativeScriptableFlags mFlags; +}; + +/***********************************************/ +// XPCWrappedNativeProto hold the additional shared wrapper data +// for XPCWrappedNative whose native objects expose nsIClassInfo. + +class XPCWrappedNativeProto final +{ +public: + static XPCWrappedNativeProto* + GetNewOrUsed(XPCWrappedNativeScope* scope, + nsIClassInfo* classInfo, + const XPCNativeScriptableCreateInfo* scriptableCreateInfo, + bool callPostCreatePrototype = true); + + XPCWrappedNativeScope* + GetScope() const {return mScope;} + + XPCJSContext* + GetContext() const {return mScope->GetContext();} + + JSObject* + GetJSProtoObject() const { return mJSProtoObject; } + + nsIClassInfo* + GetClassInfo() const {return mClassInfo;} + + XPCNativeSet* + GetSet() const {return mSet;} + + XPCNativeScriptableInfo* + GetScriptableInfo() {return mScriptableInfo;} + + bool CallPostCreatePrototype(); + void JSProtoObjectFinalized(js::FreeOp* fop, JSObject* obj); + void JSProtoObjectMoved(JSObject* obj, const JSObject* old); + + void SystemIsBeingShutDown(); + + void DebugDump(int16_t depth); + + void TraceSelf(JSTracer* trc) { + if (mJSProtoObject) + mJSProtoObject.trace(trc, "XPCWrappedNativeProto::mJSProtoObject"); + } + + void TraceInside(JSTracer* trc) { + GetScope()->TraceSelf(trc); + } + + void TraceJS(JSTracer* trc) { + TraceSelf(trc); + TraceInside(trc); + } + + void WriteBarrierPre(JSContext* cx) + { + if (JS::IsIncrementalBarrierNeeded(cx) && mJSProtoObject) + mJSProtoObject.writeBarrierPre(cx); + } + + // NOP. This is just here to make the AutoMarkingPtr code compile. + void Mark() const {} + inline void AutoTrace(JSTracer* trc) {} + + ~XPCWrappedNativeProto(); + +protected: + // disable copy ctor and assignment + XPCWrappedNativeProto(const XPCWrappedNativeProto& r) = delete; + XPCWrappedNativeProto& operator= (const XPCWrappedNativeProto& r) = delete; + + // hide ctor + XPCWrappedNativeProto(XPCWrappedNativeScope* Scope, + nsIClassInfo* ClassInfo, + already_AddRefed&& Set); + + bool Init(const XPCNativeScriptableCreateInfo* scriptableCreateInfo, + bool callPostCreatePrototype); + +private: +#ifdef DEBUG + static int32_t gDEBUG_LiveProtoCount; +#endif + +private: + XPCWrappedNativeScope* mScope; + JS::ObjectPtr mJSProtoObject; + nsCOMPtr mClassInfo; + RefPtr mSet; + XPCNativeScriptableInfo* mScriptableInfo; +}; + +/***********************************************/ +// XPCWrappedNativeTearOff represents the info needed to make calls to one +// interface on the underlying native object of a XPCWrappedNative. + +class XPCWrappedNativeTearOff final +{ +public: + bool IsAvailable() const {return mInterface == nullptr;} + bool IsReserved() const {return mInterface == (XPCNativeInterface*)1;} + bool IsValid() const {return !IsAvailable() && !IsReserved();} + void SetReserved() {mInterface = (XPCNativeInterface*)1;} + + XPCNativeInterface* GetInterface() const {return mInterface;} + nsISupports* GetNative() const {return mNative;} + JSObject* GetJSObject(); + JSObject* GetJSObjectPreserveColor() const; + void SetInterface(XPCNativeInterface* Interface) {mInterface = Interface;} + void SetNative(nsISupports* Native) {mNative = Native;} + already_AddRefed TakeNative() { return mNative.forget(); } + void SetJSObject(JSObject* JSObj); + + void JSObjectFinalized() {SetJSObject(nullptr);} + void JSObjectMoved(JSObject* obj, const JSObject* old); + + XPCWrappedNativeTearOff() + : mInterface(nullptr), mJSObject(nullptr) + { + MOZ_COUNT_CTOR(XPCWrappedNativeTearOff); + } + ~XPCWrappedNativeTearOff(); + + // NOP. This is just here to make the AutoMarkingPtr code compile. + inline void TraceJS(JSTracer* trc) {} + inline void AutoTrace(JSTracer* trc) {} + + void Mark() {mJSObject.setFlags(1);} + void Unmark() {mJSObject.unsetFlags(1);} + bool IsMarked() const {return mJSObject.hasFlag(1);} + + XPCWrappedNativeTearOff* AddTearOff() + { + MOZ_ASSERT(!mNextTearOff); + mNextTearOff = mozilla::MakeUnique(); + return mNextTearOff.get(); + } + + XPCWrappedNativeTearOff* GetNextTearOff() {return mNextTearOff.get();} + +private: + XPCWrappedNativeTearOff(const XPCWrappedNativeTearOff& r) = delete; + XPCWrappedNativeTearOff& operator= (const XPCWrappedNativeTearOff& r) = delete; + +private: + XPCNativeInterface* mInterface; + // mNative is an nsRefPtr not an nsCOMPtr because it may not be the canonical + // nsISupports pointer. + RefPtr mNative; + JS::TenuredHeap mJSObject; + mozilla::UniquePtr mNextTearOff; +}; + + +/***************************************************************************/ +// XPCWrappedNative the wrapper around one instance of a native xpcom object +// to be used from JavaScript. + +class XPCWrappedNative final : public nsIXPConnectWrappedNative +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIXPCONNECTJSOBJECTHOLDER + NS_DECL_NSIXPCONNECTWRAPPEDNATIVE + + NS_DECL_CYCLE_COLLECTION_CLASS(XPCWrappedNative) + + nsIPrincipal* GetObjectPrincipal() const; + + bool + IsValid() const { return mFlatJSObject.hasFlag(FLAT_JS_OBJECT_VALID); } + +#define XPC_SCOPE_WORD(s) (intptr_t(s)) +#define XPC_SCOPE_MASK (intptr_t(0x3)) +#define XPC_SCOPE_TAG (intptr_t(0x1)) +#define XPC_WRAPPER_EXPIRED (intptr_t(0x2)) + + static inline bool + IsTaggedScope(XPCWrappedNativeScope* s) + {return XPC_SCOPE_WORD(s) & XPC_SCOPE_TAG;} + + static inline XPCWrappedNativeScope* + TagScope(XPCWrappedNativeScope* s) + {MOZ_ASSERT(!IsTaggedScope(s), "bad pointer!"); + return (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(s) | XPC_SCOPE_TAG);} + + static inline XPCWrappedNativeScope* + UnTagScope(XPCWrappedNativeScope* s) + {return (XPCWrappedNativeScope*)(XPC_SCOPE_WORD(s) & ~XPC_SCOPE_TAG);} + + inline bool + IsWrapperExpired() const + {return XPC_SCOPE_WORD(mMaybeScope) & XPC_WRAPPER_EXPIRED;} + + bool + HasProto() const {return !IsTaggedScope(mMaybeScope);} + + XPCWrappedNativeProto* + GetProto() const + {return HasProto() ? + (XPCWrappedNativeProto*) + (XPC_SCOPE_WORD(mMaybeProto) & ~XPC_SCOPE_MASK) : nullptr;} + + void SetProto(XPCWrappedNativeProto* p); + + XPCWrappedNativeScope* + GetScope() const + {return GetProto() ? GetProto()->GetScope() : + (XPCWrappedNativeScope*) + (XPC_SCOPE_WORD(mMaybeScope) & ~XPC_SCOPE_MASK);} + + nsISupports* + GetIdentityObject() const {return mIdentity;} + + /** + * This getter clears the gray bit before handing out the JSObject which + * means that the object is guaranteed to be kept alive past the next CC. + */ + JSObject* GetFlatJSObject() const { return mFlatJSObject; } + + /** + * This getter does not change the color of the JSObject meaning that the + * object returned is not guaranteed to be kept alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without + * being rooted (or otherwise signaling the stored value to the CC). + */ + JSObject* + GetFlatJSObjectPreserveColor() const { + return mFlatJSObject.unbarrieredGetPtr(); + } + + XPCNativeSet* + GetSet() const {return mSet;} + + void + SetSet(already_AddRefed set) {mSet = set;} + + static XPCWrappedNative* Get(JSObject* obj) { + MOZ_ASSERT(IS_WN_REFLECTOR(obj)); + return (XPCWrappedNative*)js::GetObjectPrivate(obj); + } + +private: + inline void + ExpireWrapper() + {mMaybeScope = (XPCWrappedNativeScope*) + (XPC_SCOPE_WORD(mMaybeScope) | XPC_WRAPPER_EXPIRED);} + +public: + + XPCNativeScriptableInfo* + GetScriptableInfo() const {return mScriptableInfo;} + + nsIXPCScriptable* // call this wrong and you deserve to crash + GetScriptableCallback() const {return mScriptableInfo->GetCallback();} + + nsIClassInfo* + GetClassInfo() const {return IsValid() && HasProto() ? + GetProto()->GetClassInfo() : nullptr;} + + bool + HasMutatedSet() const {return IsValid() && + (!HasProto() || + GetSet() != GetProto()->GetSet());} + + XPCJSContext* + GetContext() const {XPCWrappedNativeScope* scope = GetScope(); + return scope ? scope->GetContext() : nullptr;} + + static nsresult + WrapNewGlobal(xpcObjectHelper& nativeHelper, + nsIPrincipal* principal, bool initStandardClasses, + JS::CompartmentOptions& aOptions, + XPCWrappedNative** wrappedGlobal); + + static nsresult + GetNewOrUsed(xpcObjectHelper& helper, + XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + XPCWrappedNative** wrapper); + + static nsresult + GetUsedOnly(nsISupports* Object, + XPCWrappedNativeScope* Scope, + XPCNativeInterface* Interface, + XPCWrappedNative** wrapper); + + void FlatJSObjectFinalized(); + void FlatJSObjectMoved(JSObject* obj, const JSObject* old); + + void SystemIsBeingShutDown(); + + enum CallMode {CALL_METHOD, CALL_GETTER, CALL_SETTER}; + + static bool CallMethod(XPCCallContext& ccx, + CallMode mode = CALL_METHOD); + + static bool GetAttribute(XPCCallContext& ccx) + {return CallMethod(ccx, CALL_GETTER);} + + static bool SetAttribute(XPCCallContext& ccx) + {return CallMethod(ccx, CALL_SETTER);} + + inline bool HasInterfaceNoQI(const nsIID& iid); + + XPCWrappedNativeTearOff* FindTearOff(XPCNativeInterface* aInterface, + bool needJSObject = false, + nsresult* pError = nullptr); + XPCWrappedNativeTearOff* FindTearOff(const nsIID& iid); + + void Mark() const {} + + inline void TraceInside(JSTracer* trc) { + if (HasProto()) + GetProto()->TraceSelf(trc); + else + GetScope()->TraceSelf(trc); + + JSObject* obj = mFlatJSObject.unbarrieredGetPtr(); + if (obj && JS_IsGlobalObject(obj)) { + xpc::TraceXPCGlobal(trc, obj); + } + } + + void TraceJS(JSTracer* trc) { + TraceInside(trc); + } + + void TraceSelf(JSTracer* trc) { + // If this got called, we're being kept alive by someone who really + // needs us alive and whole. Do not let our mFlatJSObject go away. + // This is the only time we should be tracing our mFlatJSObject, + // normally somebody else is doing that. + JS::TraceEdge(trc, &mFlatJSObject, "XPCWrappedNative::mFlatJSObject"); + } + + static void Trace(JSTracer* trc, JSObject* obj); + + void AutoTrace(JSTracer* trc) { + TraceSelf(trc); + } + + inline void SweepTearOffs(); + + // Returns a string that shuld be free'd using JS_smprintf_free (or null). + char* ToString(XPCWrappedNativeTearOff* to = nullptr) const; + + static void GatherProtoScriptableCreateInfo(nsIClassInfo* classInfo, + XPCNativeScriptableCreateInfo& sciProto); + + bool HasExternalReference() const {return mRefCnt > 1;} + + void Suspect(nsCycleCollectionNoteRootCallback& cb); + void NoteTearoffs(nsCycleCollectionTraversalCallback& cb); + + // Make ctor and dtor protected (rather than private) to placate nsCOMPtr. +protected: + XPCWrappedNative() = delete; + + // This ctor is used if this object will have a proto. + XPCWrappedNative(already_AddRefed&& aIdentity, + XPCWrappedNativeProto* aProto); + + // This ctor is used if this object will NOT have a proto. + XPCWrappedNative(already_AddRefed&& aIdentity, + XPCWrappedNativeScope* aScope, + already_AddRefed&& aSet); + + virtual ~XPCWrappedNative(); + void Destroy(); + + void UpdateScriptableInfo(XPCNativeScriptableInfo* si); + +private: + enum { + // Flags bits for mFlatJSObject: + FLAT_JS_OBJECT_VALID = JS_BIT(0) + }; + + bool Init(const XPCNativeScriptableCreateInfo* sci); + bool FinishInit(); + + bool ExtendSet(XPCNativeInterface* aInterface); + + nsresult InitTearOff(XPCWrappedNativeTearOff* aTearOff, + XPCNativeInterface* aInterface, + bool needJSObject); + + bool InitTearOffJSObject(XPCWrappedNativeTearOff* to); + +public: + static const XPCNativeScriptableCreateInfo& GatherScriptableCreateInfo(nsISupports* obj, + nsIClassInfo* classInfo, + XPCNativeScriptableCreateInfo& sciProto, + XPCNativeScriptableCreateInfo& sciWrapper); + +private: + union + { + XPCWrappedNativeScope* mMaybeScope; + XPCWrappedNativeProto* mMaybeProto; + }; + RefPtr mSet; + JS::TenuredHeap mFlatJSObject; + XPCNativeScriptableInfo* mScriptableInfo; + XPCWrappedNativeTearOff mFirstTearOff; +}; + +/*************************************************************************** +**************************************************************************** +* +* Core classes for wrapped JSObject for use from native code... +* +**************************************************************************** +***************************************************************************/ + +// this interfaces exists so we can refcount nsXPCWrappedJSClass +// {2453EBA0-A9B8-11d2-BA64-00805F8A5DD7} +#define NS_IXPCONNECT_WRAPPED_JS_CLASS_IID \ +{ 0x2453eba0, 0xa9b8, 0x11d2, \ + { 0xba, 0x64, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 } } + +class nsIXPCWrappedJSClass : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IXPCONNECT_WRAPPED_JS_CLASS_IID) + NS_IMETHOD DebugDump(int16_t depth) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIXPCWrappedJSClass, + NS_IXPCONNECT_WRAPPED_JS_CLASS_IID) + +/*************************/ +// nsXPCWrappedJSClass represents the sharable factored out common code and +// data for nsXPCWrappedJS instances for the same interface type. + +class nsXPCWrappedJSClass final : public nsIXPCWrappedJSClass +{ + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_IMETHOD DebugDump(int16_t depth) override; +public: + + static already_AddRefed + GetNewOrUsed(JSContext* cx, + REFNSIID aIID, + bool allowNonScriptable = false); + + REFNSIID GetIID() const {return mIID;} + XPCJSContext* GetContext() const {return mContext;} + nsIInterfaceInfo* GetInterfaceInfo() const {return mInfo;} + const char* GetInterfaceName(); + + static bool IsWrappedJS(nsISupports* aPtr); + + NS_IMETHOD DelegatedQueryInterface(nsXPCWrappedJS* self, REFNSIID aIID, + void** aInstancePtr); + + JSObject* GetRootJSObject(JSContext* cx, JSObject* aJSObj); + + NS_IMETHOD CallMethod(nsXPCWrappedJS* wrapper, uint16_t methodIndex, + const XPTMethodDescriptor* info, + nsXPTCMiniVariant* params); + + JSObject* CallQueryInterfaceOnJSObject(JSContext* cx, + JSObject* jsobj, REFNSIID aIID); + + static nsresult BuildPropertyEnumerator(XPCCallContext& ccx, + JSObject* aJSObj, + nsISimpleEnumerator** aEnumerate); + + static nsresult GetNamedPropertyAsVariant(XPCCallContext& ccx, + JSObject* aJSObj, + const nsAString& aName, + nsIVariant** aResult); + +private: + // aSyntheticException, if not null, is the exception we should be using. + // If null, look for an exception on the JSContext hanging off the + // XPCCallContext. + static nsresult CheckForException(XPCCallContext & ccx, + mozilla::dom::AutoEntryScript& aes, + const char * aPropertyName, + const char * anInterfaceName, + nsIException* aSyntheticException = nullptr); + virtual ~nsXPCWrappedJSClass(); + + nsXPCWrappedJSClass() = delete; + nsXPCWrappedJSClass(JSContext* cx, REFNSIID aIID, + nsIInterfaceInfo* aInfo); + + bool IsReflectable(uint16_t i) const + {return (bool)(mDescriptors[i/32] & (1 << (i%32)));} + void SetReflectable(uint16_t i, bool b) + {if (b) mDescriptors[i/32] |= (1 << (i%32)); + else mDescriptors[i/32] &= ~(1 << (i%32));} + + bool GetArraySizeFromParam(JSContext* cx, + const XPTMethodDescriptor* method, + const nsXPTParamInfo& param, + uint16_t methodIndex, + uint8_t paramIndex, + nsXPTCMiniVariant* params, + uint32_t* result) const; + + bool GetInterfaceTypeFromParam(JSContext* cx, + const XPTMethodDescriptor* method, + const nsXPTParamInfo& param, + uint16_t methodIndex, + const nsXPTType& type, + nsXPTCMiniVariant* params, + nsID* result) const; + + static void CleanupPointerArray(const nsXPTType& datum_type, + uint32_t array_count, + void** arrayp); + + static void CleanupPointerTypeObject(const nsXPTType& type, + void** pp); + + void CleanupOutparams(JSContext* cx, uint16_t methodIndex, const nsXPTMethodInfo* info, + nsXPTCMiniVariant* nativeParams, bool inOutOnly, uint8_t n) const; + +private: + XPCJSContext* mContext; + nsCOMPtr mInfo; + char* mName; + nsIID mIID; + uint32_t* mDescriptors; +}; + +/*************************/ +// nsXPCWrappedJS is a wrapper for a single JSObject for use from native code. +// nsXPCWrappedJS objects are chained together to represent the various +// interface on the single underlying (possibly aggregate) JSObject. + +class nsXPCWrappedJS final : protected nsAutoXPTCStub, + public nsIXPConnectWrappedJSUnmarkGray, + public nsSupportsWeakReference, + public nsIPropertyBag, + public XPCRootSetElem +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIXPCONNECTJSOBJECTHOLDER + NS_DECL_NSIXPCONNECTWRAPPEDJS + NS_DECL_NSIXPCONNECTWRAPPEDJSUNMARKGRAY + NS_DECL_NSISUPPORTSWEAKREFERENCE + NS_DECL_NSIPROPERTYBAG + + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(nsXPCWrappedJS, nsIXPConnectWrappedJS) + + NS_IMETHOD CallMethod(uint16_t methodIndex, + const XPTMethodDescriptor* info, + nsXPTCMiniVariant* params) override; + + /* + * This is rarely called directly. Instead one usually calls + * XPCConvert::JSObject2NativeInterface which will handles cases where the + * JS object is already a wrapped native or a DOM object. + */ + + static nsresult + GetNewOrUsed(JS::HandleObject aJSObj, + REFNSIID aIID, + nsXPCWrappedJS** wrapper); + + nsISomeInterface* GetXPTCStub() { return mXPTCStub; } + + /** + * This getter does not change the color of the JSObject meaning that the + * object returned is not guaranteed to be kept alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without + * being rooted (or otherwise signaling the stored value to the CC). + */ + JSObject* GetJSObjectPreserveColor() const { return mJSObj.unbarrieredGet(); } + + // Returns true if the wrapper chain contains references to multiple + // compartments. If the wrapper chain contains references to multiple + // compartments, then it must be registered on the XPCJSContext. Otherwise, + // it should be registered in the CompartmentPrivate for the compartment of + // the root's JS object. This will only return correct results when called + // on the root wrapper and will assert if not called on a root wrapper. + bool IsMultiCompartment() const; + + nsXPCWrappedJSClass* GetClass() const {return mClass;} + REFNSIID GetIID() const {return GetClass()->GetIID();} + nsXPCWrappedJS* GetRootWrapper() const {return mRoot;} + nsXPCWrappedJS* GetNextWrapper() const {return mNext;} + + nsXPCWrappedJS* Find(REFNSIID aIID); + nsXPCWrappedJS* FindInherited(REFNSIID aIID); + nsXPCWrappedJS* FindOrFindInherited(REFNSIID aIID) { + nsXPCWrappedJS* wrapper = Find(aIID); + if (wrapper) + return wrapper; + return FindInherited(aIID); + } + + bool IsRootWrapper() const { return mRoot == this; } + bool IsValid() const { return bool(mJSObj); } + void SystemIsBeingShutDown(); + + // These two methods are used by JSObject2WrappedJSMap::FindDyingJSObjects + // to find non-rooting wrappers for dying JS objects. See the top of + // XPCWrappedJS.cpp for more details. + bool IsSubjectToFinalization() const {return IsValid() && mRefCnt == 1;} + void UpdateObjectPointerAfterGC() {JS_UpdateWeakPointerAfterGC(&mJSObj);} + + bool IsAggregatedToNative() const {return mRoot->mOuter != nullptr;} + nsISupports* GetAggregatedNativeObject() const {return mRoot->mOuter;} + void SetAggregatedNativeObject(nsISupports* aNative) { + MOZ_ASSERT(aNative); + if (mRoot->mOuter) { + MOZ_ASSERT(mRoot->mOuter == aNative, + "Only one aggregated native can be set"); + return; + } + mRoot->mOuter = aNative; + } + + void TraceJS(JSTracer* trc); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + virtual ~nsXPCWrappedJS(); +protected: + nsXPCWrappedJS() = delete; + nsXPCWrappedJS(JSContext* cx, + JSObject* aJSObj, + nsXPCWrappedJSClass* aClass, + nsXPCWrappedJS* root, + nsresult* rv); + + bool CanSkip(); + void Destroy(); + void Unlink(); + +private: + JSCompartment* Compartment() const { + return js::GetObjectCompartment(mJSObj.unbarrieredGet()); + } + + JS::Heap mJSObj; + RefPtr mClass; + nsXPCWrappedJS* mRoot; // If mRoot != this, it is an owning pointer. + nsXPCWrappedJS* mNext; + nsCOMPtr mOuter; // only set in root +}; + +/***************************************************************************/ + +class XPCJSObjectHolder final : public nsIXPConnectJSObjectHolder, + public XPCRootSetElem +{ +public: + // all the interface method declarations... + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCONNECTJSOBJECTHOLDER + + // non-interface implementation + +public: + void TraceJS(JSTracer* trc); + + explicit XPCJSObjectHolder(JSObject* obj); + +private: + virtual ~XPCJSObjectHolder(); + + XPCJSObjectHolder() = delete; + + JS::Heap mJSObj; +}; + +/*************************************************************************** +**************************************************************************** +* +* All manner of utility classes follow... +* +**************************************************************************** +***************************************************************************/ + +class xpcProperty : public nsIProperty +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTY + + xpcProperty(const char16_t* aName, uint32_t aNameLen, nsIVariant* aValue); + +private: + virtual ~xpcProperty() {} + + nsString mName; + nsCOMPtr mValue; +}; + +/***************************************************************************/ +// class here just for static methods +class XPCConvert +{ +public: + static bool IsMethodReflectable(const XPTMethodDescriptor& info); + + /** + * Convert a native object into a JS::Value. + * + * @param d [out] the resulting JS::Value + * @param s the native object we're working with + * @param type the type of object that s is + * @param iid the interface of s that we want + * @param scope the default scope to put on the new JSObject's parent + * chain + * @param pErr [out] relevant error code, if any. + */ + + static bool NativeData2JS(JS::MutableHandleValue d, + const void* s, const nsXPTType& type, + const nsID* iid, nsresult* pErr); + + static bool JSData2Native(void* d, JS::HandleValue s, + const nsXPTType& type, + const nsID* iid, + nsresult* pErr); + + /** + * Convert a native nsISupports into a JSObject. + * + * @param dest [out] the resulting JSObject + * @param src the native object we're working with + * @param iid the interface of src that we want (may be null) + * @param cache the wrapper cache for src (may be null, in which case src + * will be QI'ed to get the cache) + * @param allowNativeWrapper if true, this method may wrap the resulting + * JSObject in an XPCNativeWrapper and return that, as needed. + * @param pErr [out] relevant error code, if any. + * @param src_is_identity optional performance hint. Set to true only + * if src is the identity pointer. + */ + static bool NativeInterface2JSObject(JS::MutableHandleValue d, + nsIXPConnectJSObjectHolder** dest, + xpcObjectHelper& aHelper, + const nsID* iid, + bool allowNativeWrapper, + nsresult* pErr); + + static bool GetNativeInterfaceFromJSObject(void** dest, JSObject* src, + const nsID* iid, + nsresult* pErr); + static bool JSObject2NativeInterface(void** dest, JS::HandleObject src, + const nsID* iid, + nsISupports* aOuter, + nsresult* pErr); + + // Note - This return the XPCWrappedNative, rather than the native itself, + // for the WN case. You probably want UnwrapReflectorToISupports. + static bool GetISupportsFromJSObject(JSObject* obj, nsISupports** iface); + + /** + * Convert a native array into a JS::Value. + * + * @param d [out] the resulting JS::Value + * @param s the native array we're working with + * @param type the type of objects in the array + * @param iid the interface of each object in the array that we want + * @param count the number of items in the array + * @param scope the default scope to put on the new JSObjects' parent chain + * @param pErr [out] relevant error code, if any. + */ + static bool NativeArray2JS(JS::MutableHandleValue d, const void** s, + const nsXPTType& type, const nsID* iid, + uint32_t count, nsresult* pErr); + + static bool JSArray2Native(void** d, JS::HandleValue s, + uint32_t count, const nsXPTType& type, + const nsID* iid, nsresult* pErr); + + static bool JSTypedArray2Native(void** d, + JSObject* jsarray, + uint32_t count, + const nsXPTType& type, + nsresult* pErr); + + static bool NativeStringWithSize2JS(JS::MutableHandleValue d, const void* s, + const nsXPTType& type, + uint32_t count, + nsresult* pErr); + + static bool JSStringWithSize2Native(void* d, JS::HandleValue s, + uint32_t count, const nsXPTType& type, + nsresult* pErr); + + static nsresult JSValToXPCException(JS::MutableHandleValue s, + const char* ifaceName, + const char* methodName, + nsIException** exception); + + static nsresult ConstructException(nsresult rv, const char* message, + const char* ifaceName, + const char* methodName, + nsISupports* data, + nsIException** exception, + JSContext* cx, + JS::Value* jsExceptionPtr); + +private: + XPCConvert() = delete; + +}; + +/***************************************************************************/ +// code for throwing exceptions into JS + +class nsXPCException; + +class XPCThrower +{ +public: + static void Throw(nsresult rv, JSContext* cx); + static void Throw(nsresult rv, XPCCallContext& ccx); + static void ThrowBadResult(nsresult rv, nsresult result, XPCCallContext& ccx); + static void ThrowBadParam(nsresult rv, unsigned paramNum, XPCCallContext& ccx); + static bool SetVerbosity(bool state) + {bool old = sVerbose; sVerbose = state; return old;} + + static bool CheckForPendingException(nsresult result, JSContext* cx); + +private: + static void Verbosify(XPCCallContext& ccx, + char** psz, bool own); + +private: + static bool sVerbose; +}; + +/***************************************************************************/ + +class nsXPCException +{ +public: + static bool NameAndFormatForNSResult(nsresult rv, + const char** name, + const char** format); + + static const void* IterateNSResults(nsresult* rv, + const char** name, + const char** format, + const void** iterp); + + static uint32_t GetNSResultCount(); +}; + +/***************************************************************************/ +/* +* nsJSID implements nsIJSID. It is also used by nsJSIID and nsJSCID as a +* member (as a hidden implementaion detail) to which they delegate many calls. +*/ + +// Initialization is done on demand, and calling the destructor below is always +// safe. +extern void xpc_DestroyJSxIDClassObjects(); + +class nsJSID final : public nsIJSID +{ +public: + NS_DEFINE_STATIC_CID_ACCESSOR(NS_JS_ID_CID) + + NS_DECL_ISUPPORTS + NS_DECL_NSIJSID + + bool InitWithName(const nsID& id, const char* nameString); + bool SetName(const char* name); + void SetNameToNoString() + {MOZ_ASSERT(!mName, "name already set"); mName = const_cast(gNoString);} + bool NameIsSet() const {return nullptr != mName;} + const nsID& ID() const {return mID;} + bool IsValid() const {return !mID.Equals(GetInvalidIID());} + + static already_AddRefed NewID(const char* str); + static already_AddRefed NewID(const nsID& id); + + nsJSID(); + + void Reset(); + const nsID& GetInvalidIID() const; + +protected: + virtual ~nsJSID(); + static const char gNoString[]; + nsID mID; + char* mNumber; + char* mName; +}; + + +// nsJSIID + +class nsJSIID : public nsIJSIID, + public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + + // we manually delagate these to nsJSID + NS_DECL_NSIJSID + + // we implement the rest... + NS_DECL_NSIJSIID + NS_DECL_NSIXPCSCRIPTABLE + + static already_AddRefed NewID(nsIInterfaceInfo* aInfo); + + explicit nsJSIID(nsIInterfaceInfo* aInfo); + nsJSIID() = delete; + +private: + virtual ~nsJSIID(); + + nsCOMPtr mInfo; +}; + +// nsJSCID + +class nsJSCID : public nsIJSCID, public nsIXPCScriptable +{ +public: + NS_DECL_ISUPPORTS + + // we manually delagate these to nsJSID + NS_DECL_NSIJSID + + // we implement the rest... + NS_DECL_NSIJSCID + NS_DECL_NSIXPCSCRIPTABLE + + static already_AddRefed NewID(const char* str); + + nsJSCID(); + +private: + virtual ~nsJSCID(); + + void ResolveName(); + +private: + RefPtr mDetails; +}; + + +/***************************************************************************/ +// 'Components' object implementations. nsXPCComponentsBase has the +// less-privileged stuff that we're willing to expose to XBL. + +class nsXPCComponentsBase : public nsIXPCComponentsBase +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIXPCCOMPONENTSBASE + +public: + void SystemIsBeingShutDown() { ClearMembers(); } + + XPCWrappedNativeScope* GetScope() { return mScope; } + +protected: + virtual ~nsXPCComponentsBase(); + + explicit nsXPCComponentsBase(XPCWrappedNativeScope* aScope); + virtual void ClearMembers(); + + XPCWrappedNativeScope* mScope; + + // Unprivileged members from nsIXPCComponentsBase. + RefPtr mInterfaces; + RefPtr mInterfacesByID; + RefPtr mResults; + + friend class XPCWrappedNativeScope; +}; + +class nsXPCComponents : public nsXPCComponentsBase, + public nsIXPCComponents +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_FORWARD_NSIXPCCOMPONENTSBASE(nsXPCComponentsBase::) + NS_DECL_NSIXPCCOMPONENTS + +protected: + explicit nsXPCComponents(XPCWrappedNativeScope* aScope); + virtual ~nsXPCComponents(); + virtual void ClearMembers() override; + + // Privileged members added by nsIXPCComponents. + RefPtr mClasses; + RefPtr mClassesByID; + RefPtr mID; + RefPtr mException; + RefPtr mConstructor; + RefPtr mUtils; + + friend class XPCWrappedNativeScope; +}; + + +/***************************************************************************/ + +extern JSObject* +xpc_NewIDObject(JSContext* cx, JS::HandleObject jsobj, const nsID& aID); + +extern const nsID* +xpc_JSObjectToID(JSContext* cx, JSObject* obj); + +extern bool +xpc_JSObjectIsID(JSContext* cx, JSObject* obj); + +/***************************************************************************/ +// in XPCDebug.cpp + +extern bool +xpc_DumpJSStack(bool showArgs, bool showLocals, bool showThisProps); + +// Return a newly-allocated string containing a representation of the +// current JS stack. It is the *caller's* responsibility to free this +// string with JS_smprintf_free(). +extern char* +xpc_PrintJSStack(JSContext* cx, bool showArgs, bool showLocals, + bool showThisProps); + +/***************************************************************************/ + +// Definition of nsScriptError, defined here because we lack a place to put +// XPCOM objects associated with the JavaScript engine. +class nsScriptErrorBase : public nsIScriptError { +public: + nsScriptErrorBase(); + + // TODO - do something reasonable on getting null from these babies. + + NS_DECL_NSICONSOLEMESSAGE + NS_DECL_NSISCRIPTERROR + +protected: + virtual ~nsScriptErrorBase(); + + void + InitializeOnMainThread(); + + nsString mMessage; + nsString mMessageName; + nsString mSourceName; + uint32_t mLineNumber; + nsString mSourceLine; + uint32_t mColumnNumber; + uint32_t mFlags; + nsCString mCategory; + // mOuterWindowID is set on the main thread from InitializeOnMainThread(). + uint64_t mOuterWindowID; + uint64_t mInnerWindowID; + int64_t mTimeStamp; + // mInitializedOnMainThread and mIsFromPrivateWindow are set on the main + // thread from InitializeOnMainThread(). + mozilla::Atomic mInitializedOnMainThread; + bool mIsFromPrivateWindow; +}; + +class nsScriptError final : public nsScriptErrorBase { +public: + nsScriptError() {} + NS_DECL_THREADSAFE_ISUPPORTS + +private: + virtual ~nsScriptError() {} +}; + +class nsScriptErrorWithStack : public nsScriptErrorBase { +public: + explicit nsScriptErrorWithStack(JS::HandleObject); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsScriptErrorWithStack) + + NS_IMETHOD Init(const nsAString& message, + const nsAString& sourceName, + const nsAString& sourceLine, + uint32_t lineNumber, + uint32_t columnNumber, + uint32_t flags, + const char* category) override; + + NS_IMETHOD GetStack(JS::MutableHandleValue) override; + NS_IMETHOD ToString(nsACString& aResult) override; + +private: + virtual ~nsScriptErrorWithStack(); + // Complete stackframe where the error happened. + // Must be SavedFrame object. + JS::Heap mStack; +}; + +/****************************************************************************** + * Handles pre/post script processing. + */ +class MOZ_RAII AutoScriptEvaluate +{ +public: + /** + * Saves the JSContext as well as initializing our state + * @param cx The JSContext, this can be null, we don't do anything then + */ + explicit AutoScriptEvaluate(JSContext * cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) + : mJSContext(cx), mEvaluated(false) { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + + /** + * Does the pre script evaluation. + * This function should only be called once, and will assert if called + * more than once + */ + + bool StartEvaluating(JS::HandleObject scope); + + /** + * Does the post script evaluation. + */ + ~AutoScriptEvaluate(); +private: + JSContext* mJSContext; + mozilla::Maybe mState; + bool mEvaluated; + mozilla::Maybe mAutoCompartment; + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER + + // No copying or assignment allowed + AutoScriptEvaluate(const AutoScriptEvaluate&) = delete; + AutoScriptEvaluate & operator =(const AutoScriptEvaluate&) = delete; +}; + +/***************************************************************************/ +class MOZ_RAII AutoResolveName +{ +public: + AutoResolveName(XPCCallContext& ccx, JS::HandleId name + MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : + mOld(ccx, XPCJSContext::Get()->SetResolveName(name)) +#ifdef DEBUG + ,mCheck(ccx, name) +#endif + { + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + } + ~AutoResolveName() + { +#ifdef DEBUG + jsid old = +#endif + XPCJSContext::Get()->SetResolveName(mOld); + MOZ_ASSERT(old == mCheck, "Bad Nesting!"); + } + +private: + JS::RootedId mOld; +#ifdef DEBUG + JS::RootedId mCheck; +#endif + MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER +}; + +/***************************************************************************/ +// AutoMarkingPtr is the base class for the various AutoMarking pointer types +// below. This system allows us to temporarily protect instances of our garbage +// collected types after they are constructed but before they are safely +// attached to other rooted objects. +// This base class has pure virtual support for marking. + +class AutoMarkingPtr +{ + public: + explicit AutoMarkingPtr(JSContext* cx) { + mRoot = XPCJSContext::Get()->GetAutoRootsAdr(); + mNext = *mRoot; + *mRoot = this; + } + + virtual ~AutoMarkingPtr() { + if (mRoot) { + MOZ_ASSERT(*mRoot == this); + *mRoot = mNext; + } + } + + void TraceJSAll(JSTracer* trc) { + for (AutoMarkingPtr* cur = this; cur; cur = cur->mNext) + cur->TraceJS(trc); + } + + void MarkAfterJSFinalizeAll() { + for (AutoMarkingPtr* cur = this; cur; cur = cur->mNext) + cur->MarkAfterJSFinalize(); + } + + protected: + virtual void TraceJS(JSTracer* trc) = 0; + virtual void MarkAfterJSFinalize() = 0; + + private: + AutoMarkingPtr** mRoot; + AutoMarkingPtr* mNext; +}; + +template +class TypedAutoMarkingPtr : public AutoMarkingPtr +{ + public: + explicit TypedAutoMarkingPtr(JSContext* cx) : AutoMarkingPtr(cx), mPtr(nullptr) {} + TypedAutoMarkingPtr(JSContext* cx, T* ptr) : AutoMarkingPtr(cx), mPtr(ptr) {} + + T* get() const { return mPtr; } + operator T*() const { return mPtr; } + T* operator->() const { return mPtr; } + + TypedAutoMarkingPtr& operator =(T* ptr) { mPtr = ptr; return *this; } + + protected: + virtual void TraceJS(JSTracer* trc) + { + if (mPtr) { + mPtr->TraceJS(trc); + mPtr->AutoTrace(trc); + } + } + + virtual void MarkAfterJSFinalize() + { + if (mPtr) + mPtr->Mark(); + } + + private: + T* mPtr; +}; + +typedef TypedAutoMarkingPtr AutoMarkingWrappedNativePtr; +typedef TypedAutoMarkingPtr AutoMarkingWrappedNativeTearOffPtr; +typedef TypedAutoMarkingPtr AutoMarkingWrappedNativeProtoPtr; + +/***************************************************************************/ +namespace xpc { +// Allocates a string that grants all access ("AllAccess") +char* +CloneAllAccess(); + +// Returns access if wideName is in list +char* +CheckAccessList(const char16_t* wideName, const char* const list[]); +} /* namespace xpc */ + +/***************************************************************************/ +// in xpcvariant.cpp... + +// {1809FD50-91E8-11d5-90F9-0010A4E73D9A} +#define XPCVARIANT_IID \ + {0x1809fd50, 0x91e8, 0x11d5, \ + { 0x90, 0xf9, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a } } + +// {DC524540-487E-4501-9AC7-AAA784B17C1C} +#define XPCVARIANT_CID \ + {0xdc524540, 0x487e, 0x4501, \ + { 0x9a, 0xc7, 0xaa, 0xa7, 0x84, 0xb1, 0x7c, 0x1c } } + +class XPCVariant : public nsIVariant +{ +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_NSIVARIANT + NS_DECL_CYCLE_COLLECTION_CLASS(XPCVariant) + + // If this class ever implements nsIWritableVariant, take special care with + // the case when mJSVal is JSVAL_STRING, since we don't own the data in + // that case. + + // We #define and iid so that out module local code can use QI to detect + // if a given nsIVariant is in fact an XPCVariant. + NS_DECLARE_STATIC_IID_ACCESSOR(XPCVARIANT_IID) + + static already_AddRefed newVariant(JSContext* cx, const JS::Value& aJSVal); + + /** + * This getter clears the gray bit before handing out the Value if the Value + * represents a JSObject. That means that the object is guaranteed to be + * kept alive past the next CC. + */ + JS::Value GetJSVal() const { + return mJSVal; + } + + /** + * This getter does not change the color of the Value (if it represents a + * JSObject) meaning that the value returned is not guaranteed to be kept + * alive past the next CC. + * + * This should only be called if you are certain that the return value won't + * be passed into a JS API function and that it won't be stored without + * being rooted (or otherwise signaling the stored value to the CC). + */ + JS::Value GetJSValPreserveColor() const { return mJSVal.unbarrieredGet(); } + + XPCVariant(JSContext* cx, const JS::Value& aJSVal); + + /** + * Convert a variant into a JS::Value. + * + * @param ccx the context for the whole procedure + * @param variant the variant to convert + * @param scope the default scope to put on the new JSObject's parent chain + * @param pErr [out] relevant error code, if any. + * @param pJSVal [out] the resulting jsval. + */ + static bool VariantDataToJS(nsIVariant* variant, + nsresult* pErr, JS::MutableHandleValue pJSVal); + + bool IsPurple() + { + return mRefCnt.IsPurple(); + } + + void RemovePurple() + { + mRefCnt.RemovePurple(); + } + + void SetCCGeneration(uint32_t aGen) + { + mCCGeneration = aGen; + } + + uint32_t CCGeneration() { return mCCGeneration; } +protected: + virtual ~XPCVariant() { } + + bool InitializeData(JSContext* cx); + +protected: + nsDiscriminatedUnion mData; + JS::Heap mJSVal; + bool mReturnRawObject : 1; + uint32_t mCCGeneration : 31; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(XPCVariant, XPCVARIANT_IID) + +class XPCTraceableVariant: public XPCVariant, + public XPCRootSetElem +{ +public: + XPCTraceableVariant(JSContext* cx, const JS::Value& aJSVal) + : XPCVariant(cx, aJSVal) + { + nsXPConnect::GetContextInstance()->AddVariantRoot(this); + } + + virtual ~XPCTraceableVariant(); + + void TraceJS(JSTracer* trc); +}; + +/***************************************************************************/ +// Utilities + +inline void* +xpc_GetJSPrivate(JSObject* obj) +{ + return js::GetObjectPrivate(obj); +} + +inline JSContext* +xpc_GetSafeJSContext() +{ + return XPCJSContext::Get()->Context(); +} + +namespace xpc { + +JSAddonId* +NewAddonId(JSContext* cx, const nsACString& id); + +// JSNatives to expose atob and btoa in various non-DOM XPConnect scopes. +bool +Atob(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +Btoa(JSContext* cx, unsigned argc, JS::Value* vp); + +// Helper function that creates a JSFunction that wraps a native function that +// forwards the call to the original 'callable'. +class FunctionForwarderOptions; +bool +NewFunctionForwarder(JSContext* cx, JS::HandleId id, JS::HandleObject callable, + FunctionForwarderOptions& options, JS::MutableHandleValue vp); + +// Old fashioned xpc error reporter. Try to use JS_ReportError instead. +nsresult +ThrowAndFail(nsresult errNum, JSContext* cx, bool* retval); + +struct GlobalProperties { + GlobalProperties() { + mozilla::PodZero(this); + + } + bool Parse(JSContext* cx, JS::HandleObject obj); + bool DefineInXPCComponents(JSContext* cx, JS::HandleObject obj); + bool DefineInSandbox(JSContext* cx, JS::HandleObject obj); + bool CSS : 1; + bool indexedDB : 1; + bool XMLHttpRequest : 1; + bool TextDecoder : 1; + bool TextEncoder : 1; + bool URL : 1; + bool URLSearchParams : 1; + bool atob : 1; + bool btoa : 1; + bool Blob : 1; + bool Directory : 1; + bool File : 1; + bool crypto : 1; + bool rtcIdentityProvider : 1; + bool fetch : 1; + bool caches : 1; + bool fileReader: 1; +private: + bool Define(JSContext* cx, JS::HandleObject obj); +}; + +// Infallible. +already_AddRefed +NewSandboxConstructor(); + +// Returns true if class of 'obj' is SandboxClass. +bool +IsSandbox(JSObject* obj); + +class MOZ_STACK_CLASS OptionsBase { +public: + explicit OptionsBase(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : mCx(cx) + , mObject(cx, options) + { } + + virtual bool Parse() = 0; + +protected: + bool ParseValue(const char* name, JS::MutableHandleValue prop, bool* found = nullptr); + bool ParseBoolean(const char* name, bool* prop); + bool ParseObject(const char* name, JS::MutableHandleObject prop); + bool ParseJSString(const char* name, JS::MutableHandleString prop); + bool ParseString(const char* name, nsCString& prop); + bool ParseString(const char* name, nsString& prop); + bool ParseId(const char* name, JS::MutableHandleId id); + bool ParseUInt32(const char* name, uint32_t* prop); + + JSContext* mCx; + JS::RootedObject mObject; +}; + +class MOZ_STACK_CLASS SandboxOptions : public OptionsBase { +public: + explicit SandboxOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options) + , wantXrays(true) + , allowWaivers(true) + , wantComponents(true) + , wantExportHelpers(false) + , isWebExtensionContentScript(false) + , waiveInterposition(false) + , proto(cx) + , addonId(cx) + , writeToGlobalPrototype(false) + , sameZoneAs(cx) + , freshZone(false) + , invisibleToDebugger(false) + , discardSource(false) + , metadata(cx) + , userContextId(0) + , originAttributes(cx) + { } + + virtual bool Parse(); + + bool wantXrays; + bool allowWaivers; + bool wantComponents; + bool wantExportHelpers; + bool isWebExtensionContentScript; + bool waiveInterposition; + JS::RootedObject proto; + nsCString sandboxName; + JS::RootedString addonId; + bool writeToGlobalPrototype; + JS::RootedObject sameZoneAs; + bool freshZone; + bool invisibleToDebugger; + bool discardSource; + GlobalProperties globalProperties; + JS::RootedValue metadata; + uint32_t userContextId; + JS::RootedObject originAttributes; + +protected: + bool ParseGlobalProperties(); +}; + +class MOZ_STACK_CLASS CreateObjectInOptions : public OptionsBase { +public: + explicit CreateObjectInOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options) + , defineAs(cx, JSID_VOID) + { } + + virtual bool Parse() { return ParseId("defineAs", &defineAs); } + + JS::RootedId defineAs; +}; + +class MOZ_STACK_CLASS ExportFunctionOptions : public OptionsBase { +public: + explicit ExportFunctionOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options) + , defineAs(cx, JSID_VOID) + , allowCrossOriginArguments(false) + { } + + virtual bool Parse() { + return ParseId("defineAs", &defineAs) && + ParseBoolean("allowCrossOriginArguments", &allowCrossOriginArguments); + } + + JS::RootedId defineAs; + bool allowCrossOriginArguments; +}; + +class MOZ_STACK_CLASS FunctionForwarderOptions : public OptionsBase { +public: + explicit FunctionForwarderOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options) + , allowCrossOriginArguments(false) + { } + + JSObject* ToJSObject(JSContext* cx) { + JS::RootedObject obj(cx, JS_NewObjectWithGivenProto(cx, nullptr, nullptr)); + if (!obj) + return nullptr; + + JS::RootedValue val(cx); + unsigned attrs = JSPROP_READONLY | JSPROP_PERMANENT; + val = JS::BooleanValue(allowCrossOriginArguments); + if (!JS_DefineProperty(cx, obj, "allowCrossOriginArguments", val, attrs)) + return nullptr; + + return obj; + } + + virtual bool Parse() { + return ParseBoolean("allowCrossOriginArguments", &allowCrossOriginArguments); + } + + bool allowCrossOriginArguments; +}; + +class MOZ_STACK_CLASS StackScopedCloneOptions : public OptionsBase { +public: + explicit StackScopedCloneOptions(JSContext* cx = xpc_GetSafeJSContext(), + JSObject* options = nullptr) + : OptionsBase(cx, options) + , wrapReflectors(false) + , cloneFunctions(false) + , deepFreeze(false) + { } + + virtual bool Parse() { + return ParseBoolean("wrapReflectors", &wrapReflectors) && + ParseBoolean("cloneFunctions", &cloneFunctions) && + ParseBoolean("deepFreeze", &deepFreeze); + } + + // When a reflector is encountered, wrap it rather than aborting the clone. + bool wrapReflectors; + + // When a function is encountered, clone it (exportFunction-style) rather than + // aborting the clone. + bool cloneFunctions; + + // If true, the resulting object is deep-frozen after being cloned. + bool deepFreeze; +}; + +JSObject* +CreateGlobalObject(JSContext* cx, const JSClass* clasp, nsIPrincipal* principal, + JS::CompartmentOptions& aOptions); + +// Modify the provided compartment options, consistent with |aPrincipal| and +// with globally-cached values of various preferences. +// +// Call this function *before* |aOptions| is used to create the corresponding +// global object, as not all of the options it sets can be modified on an +// existing global object. (The type system should make this obvious, because +// you can't get a *mutable* JS::CompartmentOptions& from an existing global +// object.) +void +InitGlobalObjectOptions(JS::CompartmentOptions& aOptions, + nsIPrincipal* aPrincipal); + +// Finish initializing an already-created, not-yet-exposed-to-script global +// object. This will attach a Components object (if necessary) and call +// |JS_FireOnNewGlobalObject| (if necessary). +// +// If you must modify compartment options, see InitGlobalObjectOptions above. +bool +InitGlobalObject(JSContext* aJSContext, JS::Handle aGlobal, + uint32_t aFlags); + +// Helper for creating a sandbox object to use for evaluating +// untrusted code completely separated from all other code in the +// system using EvalInSandbox(). Takes the JSContext on which to +// do setup etc on, puts the sandbox object in *vp (which must be +// rooted by the caller), and uses the principal that's either +// directly passed in prinOrSop or indirectly as an +// nsIScriptObjectPrincipal holding the principal. If no principal is +// reachable through prinOrSop, a new null principal will be created +// and used. +nsresult +CreateSandboxObject(JSContext* cx, JS::MutableHandleValue vp, nsISupports* prinOrSop, + xpc::SandboxOptions& options); +// Helper for evaluating scripts in a sandbox object created with +// CreateSandboxObject(). The caller is responsible of ensuring +// that *rval doesn't get collected during the call or usage after the +// call. This helper will use filename and lineNo for error reporting, +// and if no filename is provided it will use the codebase from the +// principal and line number 1 as a fallback. +nsresult +EvalInSandbox(JSContext* cx, JS::HandleObject sandbox, const nsAString& source, + const nsACString& filename, int32_t lineNo, + JSVersion jsVersion, JS::MutableHandleValue rval); + +nsresult +GetSandboxAddonId(JSContext* cx, JS::HandleObject sandboxArg, + JS::MutableHandleValue rval); + +// Helper for retrieving metadata stored in a reserved slot. The metadata +// is set during the sandbox creation using the "metadata" option. +nsresult +GetSandboxMetadata(JSContext* cx, JS::HandleObject sandboxArg, + JS::MutableHandleValue rval); + +nsresult +SetSandboxMetadata(JSContext* cx, JS::HandleObject sandboxArg, + JS::HandleValue metadata); + +bool +CreateObjectIn(JSContext* cx, JS::HandleValue vobj, CreateObjectInOptions& options, + JS::MutableHandleValue rval); + +bool +EvalInWindow(JSContext* cx, const nsAString& source, JS::HandleObject scope, + JS::MutableHandleValue rval); + +bool +ExportFunction(JSContext* cx, JS::HandleValue vscope, JS::HandleValue vfunction, + JS::HandleValue voptions, JS::MutableHandleValue rval); + +bool +CloneInto(JSContext* cx, JS::HandleValue vobj, JS::HandleValue vscope, + JS::HandleValue voptions, JS::MutableHandleValue rval); + +bool +StackScopedClone(JSContext* cx, StackScopedCloneOptions& options, JS::MutableHandleValue val); + +} /* namespace xpc */ + + +/***************************************************************************/ +// Inlined utilities. + +inline bool +xpc_ForcePropertyResolve(JSContext* cx, JS::HandleObject obj, jsid id); + +inline jsid +GetJSIDByIndex(JSContext* cx, unsigned index); + +namespace xpc { + +enum WrapperDenialType { + WrapperDenialForXray = 0, + WrapperDenialForCOW, + WrapperDenialTypeCount +}; +bool ReportWrapperDenial(JSContext* cx, JS::HandleId id, WrapperDenialType type, const char* reason); + +class CompartmentPrivate +{ +public: + enum LocationHint { + LocationHintRegular, + LocationHintAddon + }; + + explicit CompartmentPrivate(JSCompartment* c); + + ~CompartmentPrivate(); + + static CompartmentPrivate* Get(JSCompartment* compartment) + { + MOZ_ASSERT(compartment); + void* priv = JS_GetCompartmentPrivate(compartment); + return static_cast(priv); + } + + static CompartmentPrivate* Get(JSObject* object) + { + JSCompartment* compartment = js::GetObjectCompartment(object); + return Get(compartment); + } + + // Controls whether this compartment gets Xrays to same-origin. This behavior + // is deprecated, but is still the default for sandboxes for compatibity + // reasons. + bool wantXrays; + + // Controls whether this compartment is allowed to waive Xrays to content + // that it subsumes. This should generally be true, except in cases where we + // want to prevent code from depending on Xray Waivers (which might make it + // more portable to other browser architectures). + bool allowWaivers; + + // This flag is intended for a very specific use, internal to Gecko. It may + // go away or change behavior at any time. It should not be added to any + // documentation and it should not be used without consulting the XPConnect + // module owner. + bool writeToGlobalPrototype; + + // When writeToGlobalPrototype is true, we use this flag to temporarily + // disable the writeToGlobalPrototype behavior (when resolving standard + // classes, for example). + bool skipWriteToGlobalPrototype; + + // This scope corresponds to a WebExtension content script, and receives + // various bits of special compatibility behavior. + bool isWebExtensionContentScript; + + // Even if an add-on needs interposition, it does not necessary need it + // for every scope. If this flag is set we waive interposition for this + // scope. + bool waiveInterposition; + + // If CPOWs are disabled for browser code via the + // dom.ipc.cpows.forbid-unsafe-from-browser preferences, then only + // add-ons can use CPOWs. This flag allows a non-addon scope + // to opt into CPOWs. It's necessary for the implementation of + // RemoteAddonsParent.jsm. + bool allowCPOWs; + + // This is only ever set during mochitest runs when enablePrivilege is called. + // It's intended as a temporary stopgap measure until we can finish ripping out + // enablePrivilege. Once set, this value is never unset (i.e., it doesn't follow + // the old scoping rules of enablePrivilege). + // + // Using it in production is inherently unsafe. + bool universalXPConnectEnabled; + + // This is only ever set during mochitest runs when enablePrivilege is called. + // It allows the SpecialPowers scope to waive the normal chrome security + // wrappers and expose properties directly to content. This lets us avoid a + // bunch of overhead and complexity in our SpecialPowers automation glue. + // + // Using it in production is inherently unsafe. + bool forcePermissiveCOWs; + + // Whether we've emitted a warning about a property that was filtered out + // by a security wrapper. See XrayWrapper.cpp. + bool wrapperDenialWarnings[WrapperDenialTypeCount]; + + // The scriptability of this compartment. + Scriptability scriptability; + + // Our XPCWrappedNativeScope. This is non-null if and only if this is an + // XPConnect compartment. + XPCWrappedNativeScope* scope; + + const nsACString& GetLocation() { + if (location.IsEmpty() && locationURI) { + + nsCOMPtr jsLocationURI = + do_QueryInterface(locationURI); + if (jsLocationURI) { + // We cannot call into JS-implemented nsIURI objects, because + // we are iterating over the JS heap at this point. + location = + NS_LITERAL_CSTRING(""); + } else if (NS_FAILED(locationURI->GetSpec(location))) { + location = NS_LITERAL_CSTRING(""); + } + } + return location; + } + bool GetLocationURI(nsIURI** aURI) { + return GetLocationURI(LocationHintRegular, aURI); + } + bool GetLocationURI(LocationHint aLocationHint, nsIURI** aURI) { + if (locationURI) { + nsCOMPtr rval = locationURI; + rval.forget(aURI); + return true; + } + return TryParseLocationURI(aLocationHint, aURI); + } + void SetLocation(const nsACString& aLocation) { + if (aLocation.IsEmpty()) + return; + if (!location.IsEmpty() || locationURI) + return; + location = aLocation; + } + void SetLocationURI(nsIURI* aLocationURI) { + if (!aLocationURI) + return; + if (locationURI) + return; + locationURI = aLocationURI; + } + + JSObject2WrappedJSMap* GetWrappedJSMap() const { return mWrappedJSMap; } + void UpdateWeakPointersAfterGC(XPCJSContext* context); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf); + +private: + nsCString location; + nsCOMPtr locationURI; + JSObject2WrappedJSMap* mWrappedJSMap; + + bool TryParseLocationURI(LocationHint aType, nsIURI** aURI); +}; + +bool IsUniversalXPConnectEnabled(JSCompartment* compartment); +bool IsUniversalXPConnectEnabled(JSContext* cx); +bool EnableUniversalXPConnect(JSContext* cx); + +inline void +CrashIfNotInAutomation() +{ + MOZ_RELEASE_ASSERT(IsInAutomation()); +} + +inline XPCWrappedNativeScope* +ObjectScope(JSObject* obj) +{ + return CompartmentPrivate::Get(obj)->scope; +} + +JSObject* NewOutObject(JSContext* cx); +bool IsOutObject(JSContext* cx, JSObject* obj); + +nsresult HasInstance(JSContext* cx, JS::HandleObject objArg, const nsID* iid, bool* bp); + +nsIPrincipal* GetObjectPrincipal(JSObject* obj); + +} // namespace xpc + +namespace mozilla { +namespace dom { +extern bool +DefineStaticJSVals(JSContext* cx); +} // namespace dom +} // namespace mozilla + +bool +xpc_LocalizeContext(JSContext* cx); +void +xpc_DelocalizeContext(JSContext* cx); + +/***************************************************************************/ +// Inlines use the above - include last. + +#include "XPCInlines.h" + +/***************************************************************************/ +// Maps have inlines that use the above - include last. + +#include "XPCMaps.h" + +/***************************************************************************/ + +#endif /* xpcprivate_h___ */ diff --git a/js/xpconnect/src/xpcpublic.h b/js/xpconnect/src/xpcpublic.h new file mode 100644 index 000000000..fc8670d46 --- /dev/null +++ b/js/xpconnect/src/xpcpublic.h @@ -0,0 +1,635 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef xpcpublic_h +#define xpcpublic_h + +#include "jsapi.h" +#include "js/HeapAPI.h" +#include "js/GCAPI.h" +#include "js/Proxy.h" + +#include "nsISupports.h" +#include "nsIURI.h" +#include "nsIPrincipal.h" +#include "nsIGlobalObject.h" +#include "nsPIDOMWindow.h" +#include "nsWrapperCache.h" +#include "nsStringGlue.h" +#include "nsTArray.h" +#include "mozilla/dom/JSSlots.h" +#include "mozilla/fallible.h" +#include "nsMathUtils.h" +#include "nsStringBuffer.h" +#include "mozilla/dom/BindingDeclarations.h" +#include "mozilla/Preferences.h" + +class nsGlobalWindow; +class nsIPrincipal; +class nsScriptNameSpaceManager; +class nsIMemoryReporterCallback; + +namespace mozilla { +namespace dom { +class Exception; +} +} + +typedef void (* xpcGCCallback)(JSGCStatus status); + +namespace xpc { + +class Scriptability { +public: + explicit Scriptability(JSCompartment* c); + bool Allowed(); + bool IsImmuneToScriptPolicy(); + + void Block(); + void Unblock(); + void SetDocShellAllowsScript(bool aAllowed); + + static Scriptability& Get(JSObject* aScope); + +private: + // Whenever a consumer wishes to prevent script from running on a global, + // it increments this value with a call to Block(). When it wishes to + // re-enable it (if ever), it decrements this value with a call to Unblock(). + // Script may not run if this value is non-zero. + uint32_t mScriptBlocks; + + // Whether the docshell allows javascript in this scope. If this scope + // doesn't have a docshell, this value is always true. + bool mDocShellAllowsScript; + + // Whether this scope is immune to user-defined or addon-defined script + // policy. + bool mImmuneToScriptPolicy; + + // Whether the new-style domain policy when this compartment was created + // forbids script execution. + bool mScriptBlockedByPolicy; +}; + +JSObject* +TransplantObject(JSContext* cx, JS::HandleObject origobj, JS::HandleObject target); + +bool IsContentXBLScope(JSCompartment* compartment); +bool IsInContentXBLScope(JSObject* obj); + +// Return a raw XBL scope object corresponding to contentScope, which must +// be an object whose global is a DOM window. +// +// The return value is not wrapped into cx->compartment, so be sure to enter +// its compartment before doing anything meaningful. +// +// Also note that XBL scopes are lazily created, so the return-value should be +// null-checked unless the caller can ensure that the scope must already +// exist. +// +// This function asserts if |contentScope| is itself in an XBL scope to catch +// sloppy consumers. Conversely, GetXBLScopeOrGlobal will handle objects that +// are in XBL scope (by just returning the global). +JSObject* +GetXBLScope(JSContext* cx, JSObject* contentScope); + +inline JSObject* +GetXBLScopeOrGlobal(JSContext* cx, JSObject* obj) +{ + if (IsInContentXBLScope(obj)) + return js::GetGlobalForObjectCrossCompartment(obj); + return GetXBLScope(cx, obj); +} + +// This function is similar to GetXBLScopeOrGlobal. However, if |obj| is a +// chrome scope, then it will return an add-on scope if addonId is non-null. +// Like GetXBLScopeOrGlobal, it returns the scope of |obj| if it's already a +// content XBL scope. But it asserts that |obj| is not an add-on scope. +JSObject* +GetScopeForXBLExecution(JSContext* cx, JS::HandleObject obj, JSAddonId* addonId); + +// Returns whether XBL scopes have been explicitly disabled for code running +// in this compartment. See the comment around mAllowContentXBLScope. +bool +AllowContentXBLScope(JSCompartment* c); + +// Returns whether we will use an XBL scope for this compartment. This is +// semantically equivalent to comparing global != GetXBLScope(global), but it +// does not have the side-effect of eagerly creating the XBL scope if it does +// not already exist. +bool +UseContentXBLScope(JSCompartment* c); + +// Clear out the content XBL scope (if any) on the given global. This will +// force creation of a new one if one is needed again. +void +ClearContentXBLScope(JSObject* global); + +bool +IsInAddonScope(JSObject* obj); + +JSObject* +GetAddonScope(JSContext* cx, JS::HandleObject contentScope, JSAddonId* addonId); + +bool +IsSandboxPrototypeProxy(JSObject* obj); + +bool +IsReflector(JSObject* obj); + +bool +IsXrayWrapper(JSObject* obj); + +// If this function was created for a given XrayWrapper, returns the global of +// the Xrayed object. Otherwise, returns the global of the function. +// +// To emphasize the obvious: the return value here is not necessarily same- +// compartment with the argument. +JSObject* +XrayAwareCalleeGlobal(JSObject* fun); + +void +TraceXPCGlobal(JSTracer* trc, JSObject* obj); + +} /* namespace xpc */ + +namespace JS { + +struct RuntimeStats; + +} // namespace JS + +#define XPC_WRAPPER_FLAGS (JSCLASS_HAS_PRIVATE | JSCLASS_FOREGROUND_FINALIZE) + +#define XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(n) \ + JSCLASS_DOM_GLOBAL | JSCLASS_HAS_PRIVATE | \ + JSCLASS_PRIVATE_IS_NSISUPPORTS | \ + JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(DOM_GLOBAL_SLOTS + n) + +#define XPCONNECT_GLOBAL_EXTRA_SLOT_OFFSET (JSCLASS_GLOBAL_SLOT_COUNT + DOM_GLOBAL_SLOTS) + +#define XPCONNECT_GLOBAL_FLAGS XPCONNECT_GLOBAL_FLAGS_WITH_EXTRA_SLOTS(0) + +inline JSObject* +xpc_FastGetCachedWrapper(JSContext* cx, nsWrapperCache* cache, JS::MutableHandleValue vp) +{ + if (cache) { + JSObject* wrapper = cache->GetWrapper(); + if (wrapper && + js::GetObjectCompartment(wrapper) == js::GetContextCompartment(cx)) + { + vp.setObject(*wrapper); + return wrapper; + } + } + + return nullptr; +} + +// If aVariant is an XPCVariant, this marks the object to be in aGeneration. +// This also unmarks the gray JSObject. +extern void +xpc_MarkInCCGeneration(nsISupports* aVariant, uint32_t aGeneration); + +// If aWrappedJS is a JS wrapper, unmark its JSObject. +extern void +xpc_TryUnmarkWrappedGrayObject(nsISupports* aWrappedJS); + +extern void +xpc_UnmarkSkippableJSHolders(); + +// readable string conversions, static methods and members only +class XPCStringConvert +{ + // One-slot cache, because it turns out it's common for web pages to + // get the same string a few times in a row. We get about a 40% cache + // hit rate on this cache last it was measured. We'd get about 70% + // hit rate with a hashtable with removal on finalization, but that + // would take a lot more machinery. + struct ZoneStringCache + { + // mString owns mBuffer. mString is a JS thing, so it can only die + // during GC, though it can drop its ref to the buffer if it gets + // flattened and wasn't null-terminated. We clear mString and mBuffer + // during GC and in our finalizer (to catch the flatterning case). As + // long as the above holds, mBuffer should not be a dangling pointer, so + // using this as a cache key should be safe. + // + // We also need to include the string's length in the cache key, because + // now that we allow non-null-terminated buffers we can have two strings + // with the same mBuffer but different lengths. + void* mBuffer = nullptr; + uint32_t mLength = 0; + JSString* mString = nullptr; + }; + +public: + + // If the string shares the readable's buffer, that buffer will + // get assigned to *sharedBuffer. Otherwise null will be + // assigned. + static bool ReadableToJSVal(JSContext* cx, const nsAString& readable, + nsStringBuffer** sharedBuffer, + JS::MutableHandleValue vp); + + // Convert the given stringbuffer/length pair to a jsval + static MOZ_ALWAYS_INLINE bool + StringBufferToJSVal(JSContext* cx, nsStringBuffer* buf, uint32_t length, + JS::MutableHandleValue rval, bool* sharedBuffer) + { + JS::Zone* zone = js::GetContextZone(cx); + ZoneStringCache* cache = static_cast(JS_GetZoneUserData(zone)); + if (cache && buf == cache->mBuffer && length == cache->mLength) { + MOZ_ASSERT(JS::GetStringZone(cache->mString) == zone); + JS::MarkStringAsLive(zone, cache->mString); + rval.setString(cache->mString); + *sharedBuffer = false; + return true; + } + + JSString* str = JS_NewExternalString(cx, + static_cast(buf->Data()), + length, &sDOMStringFinalizer); + if (!str) { + return false; + } + rval.setString(str); + if (!cache) { + cache = new ZoneStringCache(); + JS_SetZoneUserData(zone, cache); + } + cache->mBuffer = buf; + cache->mLength = length; + cache->mString = str; + *sharedBuffer = true; + return true; + } + + static void FreeZoneCache(JS::Zone* zone); + static void ClearZoneCache(JS::Zone* zone); + + static MOZ_ALWAYS_INLINE bool IsLiteral(JSString* str) + { + return JS_IsExternalString(str) && + JS_GetExternalStringFinalizer(str) == &sLiteralFinalizer; + } + + static MOZ_ALWAYS_INLINE bool IsDOMString(JSString* str) + { + return JS_IsExternalString(str) && + JS_GetExternalStringFinalizer(str) == &sDOMStringFinalizer; + } + +private: + static const JSStringFinalizer sLiteralFinalizer, sDOMStringFinalizer; + + static void FinalizeLiteral(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars); + + static void FinalizeDOMString(JS::Zone* zone, const JSStringFinalizer* fin, char16_t* chars); + + XPCStringConvert() = delete; +}; + +class nsIAddonInterposition; + +namespace xpc { + +// If these functions return false, then an exception will be set on cx. +bool Base64Encode(JSContext* cx, JS::HandleValue val, JS::MutableHandleValue out); +bool Base64Decode(JSContext* cx, JS::HandleValue val, JS::MutableHandleValue out); + +/** + * Convert an nsString to jsval, returning true on success. + * Note, the ownership of the string buffer may be moved from str to rval. + * If that happens, str will point to an empty string after this call. + */ +bool NonVoidStringToJsval(JSContext* cx, nsAString& str, JS::MutableHandleValue rval); +inline bool StringToJsval(JSContext* cx, nsAString& str, JS::MutableHandleValue rval) +{ + // From the T_DOMSTRING case in XPCConvert::NativeData2JS. + if (str.IsVoid()) { + rval.setNull(); + return true; + } + return NonVoidStringToJsval(cx, str, rval); +} + +inline bool +NonVoidStringToJsval(JSContext* cx, const nsAString& str, JS::MutableHandleValue rval) +{ + nsString mutableCopy; + if (!mutableCopy.Assign(str, mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + return false; + } + return NonVoidStringToJsval(cx, mutableCopy, rval); +} + +inline bool +StringToJsval(JSContext* cx, const nsAString& str, JS::MutableHandleValue rval) +{ + nsString mutableCopy; + if (!mutableCopy.Assign(str, mozilla::fallible)) { + JS_ReportOutOfMemory(cx); + return false; + } + return StringToJsval(cx, mutableCopy, rval); +} + +/** + * As above, but for mozilla::dom::DOMString. + */ +inline +bool NonVoidStringToJsval(JSContext* cx, mozilla::dom::DOMString& str, + JS::MutableHandleValue rval) +{ + if (!str.HasStringBuffer()) { + // It's an actual XPCOM string + return NonVoidStringToJsval(cx, str.AsAString(), rval); + } + + uint32_t length = str.StringBufferLength(); + if (length == 0) { + rval.set(JS_GetEmptyStringValue(cx)); + return true; + } + + nsStringBuffer* buf = str.StringBuffer(); + bool shared; + if (!XPCStringConvert::StringBufferToJSVal(cx, buf, length, rval, + &shared)) { + return false; + } + if (shared) { + // JS now needs to hold a reference to the buffer + str.RelinquishBufferOwnership(); + } + return true; +} + +MOZ_ALWAYS_INLINE +bool StringToJsval(JSContext* cx, mozilla::dom::DOMString& str, + JS::MutableHandleValue rval) +{ + if (str.IsNull()) { + rval.setNull(); + return true; + } + return NonVoidStringToJsval(cx, str, rval); +} + +nsIPrincipal* GetCompartmentPrincipal(JSCompartment* compartment); + +void SetLocationForGlobal(JSObject* global, const nsACString& location); +void SetLocationForGlobal(JSObject* global, nsIURI* locationURI); + +// ReportJSRuntimeExplicitTreeStats will expect this in the |extra| member +// of JS::ZoneStats. +class ZoneStatsExtras { +public: + ZoneStatsExtras() {} + + nsCString pathPrefix; + +private: + ZoneStatsExtras(const ZoneStatsExtras& other) = delete; + ZoneStatsExtras& operator=(const ZoneStatsExtras& other) = delete; +}; + +// ReportJSRuntimeExplicitTreeStats will expect this in the |extra| member +// of JS::CompartmentStats. +class CompartmentStatsExtras { +public: + CompartmentStatsExtras() {} + + nsCString jsPathPrefix; + nsCString domPathPrefix; + nsCOMPtr location; + +private: + CompartmentStatsExtras(const CompartmentStatsExtras& other) = delete; + CompartmentStatsExtras& operator=(const CompartmentStatsExtras& other) = delete; +}; + +// This reports all the stats in |rtStats| that belong in the "explicit" tree, +// (which isn't all of them). +// @see ZoneStatsExtras +// @see CompartmentStatsExtras +void +ReportJSRuntimeExplicitTreeStats(const JS::RuntimeStats& rtStats, + const nsACString& rtPath, + nsIMemoryReporterCallback* handleReport, + nsISupports* data, + bool anonymize, + size_t* rtTotal = nullptr); + +/** + * Throws an exception on cx and returns false. + */ +bool +Throw(JSContext* cx, nsresult rv); + +/** + * Returns the nsISupports native behind a given reflector (either DOM or + * XPCWN). + */ +already_AddRefed +UnwrapReflectorToISupports(JSObject* reflector); + +/** + * Singleton scopes for stuff that really doesn't fit anywhere else. + * + * If you find yourself wanting to use these compartments, you're probably doing + * something wrong. Callers MUST consult with the XPConnect module owner before + * using this compartment. If you don't, bholley will hunt you down. + */ +JSObject* +UnprivilegedJunkScope(); + +JSObject* +PrivilegedJunkScope(); + +/** + * Shared compilation scope for XUL prototype documents and XBL + * precompilation. This compartment has a null principal. No code may run, and + * it is invisible to the debugger. + */ +JSObject* +CompilationScope(); + +/** + * Returns the nsIGlobalObject corresponding to |aObj|'s JS global. + */ +nsIGlobalObject* +NativeGlobal(JSObject* aObj); + +/** + * If |aObj| is a window, returns the associated nsGlobalWindow. + * Otherwise, returns null. + */ +nsGlobalWindow* +WindowOrNull(JSObject* aObj); + +/** + * If |aObj| has a window for a global, returns the associated nsGlobalWindow. + * Otherwise, returns null. + */ +nsGlobalWindow* +WindowGlobalOrNull(JSObject* aObj); + +/** + * If |aObj| is in an addon scope and that addon scope is associated with a + * live DOM Window, returns the associated nsGlobalWindow. Otherwise, returns + * null. + */ +nsGlobalWindow* +AddonWindowOrNull(JSObject* aObj); + +/** + * If |cx| is in a compartment whose global is a window, returns the associated + * nsGlobalWindow. Otherwise, returns null. + */ +nsGlobalWindow* +CurrentWindowOrNull(JSContext* cx); + +void +SimulateActivityCallback(bool aActive); + +// This function may be used off-main-thread, in which case it is benignly +// racey. +bool +ShouldDiscardSystemSource(); + +bool +SharedMemoryEnabled(); + +bool +SetAddonInterposition(const nsACString& addonId, nsIAddonInterposition* interposition); + +bool +AllowCPOWsInAddon(const nsACString& addonId, bool allow); + +bool +ExtraWarningsForSystemJS(); + +class ErrorReport { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ErrorReport); + + ErrorReport() : mWindowID(0) + , mLineNumber(0) + , mColumn(0) + , mFlags(0) + , mIsMuted(false) + {} + + void Init(JSErrorReport* aReport, const char* aToStringResult, + bool aIsChrome, uint64_t aWindowID); + void Init(JSContext* aCx, mozilla::dom::Exception* aException, + bool aIsChrome, uint64_t aWindowID); + // Log the error report to the console. Which console will depend on the + // window id it was initialized with. + void LogToConsole(); + // Log to console, using the given stack object (which should be a stack of + // the sort that JS::CaptureCurrentStack produces). aStack is allowed to be + // null. + void LogToConsoleWithStack(JS::HandleObject aStack); + + // Produce an error event message string from the given JSErrorReport. Note + // that this may produce an empty string if aReport doesn't have a + // message attached. + static void ErrorReportToMessageString(JSErrorReport* aReport, + nsAString& aString); + + public: + + nsCString mCategory; + nsString mErrorMsgName; + nsString mErrorMsg; + nsString mFileName; + nsString mSourceLine; + uint64_t mWindowID; + uint32_t mLineNumber; + uint32_t mColumn; + uint32_t mFlags; + bool mIsMuted; + + private: + ~ErrorReport() {} +}; + +void +DispatchScriptErrorEvent(nsPIDOMWindowInner* win, JS::RootingContext* rootingCx, + xpc::ErrorReport* xpcReport, JS::Handle exception); + +// Get a stack of the sort that can be passed to +// xpc::ErrorReport::LogToConsoleWithStack from the given exception value. Can +// return null if the exception value doesn't have an associated stack. The +// returned stack, if any, may also not be in the same compartment as +// exceptionValue. +// +// The "win" argument passed in here should be the same as the window whose +// WindowID() is used to initialize the xpc::ErrorReport. This may be null, of +// course. If it's not null, this function may return a null stack object if +// the window is far enough gone, because in those cases we don't want to have +// the stack in the console message keeping the window alive. +JSObject* +FindExceptionStackForConsoleReport(nsPIDOMWindowInner* win, + JS::HandleValue exceptionValue); + +// Return a name for the compartment. +// This function makes reasonable efforts to make this name both mostly human-readable +// and unique. However, there are no guarantees of either property. +extern void +GetCurrentCompartmentName(JSContext*, nsCString& name); + +void AddGCCallback(xpcGCCallback cb); +void RemoveGCCallback(xpcGCCallback cb); + +inline bool +AreNonLocalConnectionsDisabled() +{ + static int disabledForTest = -1; + if (disabledForTest == -1) { + char *s = getenv("MOZ_DISABLE_NONLOCAL_CONNECTIONS"); + if (s) { + disabledForTest = *s != '0'; + } else { + disabledForTest = 0; + } + } + return disabledForTest; +} + +inline bool +IsInAutomation() +{ + const char* prefName = + "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer"; + return mozilla::Preferences::GetBool(prefName) && + AreNonLocalConnectionsDisabled(); +} + +} // namespace xpc + +namespace mozilla { +namespace dom { + +/** + * A test for whether WebIDL methods that should only be visible to + * chrome or XBL scopes should be exposed. + */ +bool IsChromeOrXBL(JSContext* cx, JSObject* /* unused */); + +/** + * Same as IsChromeOrXBL but can be used in worker threads as well. + */ +bool ThreadSafeIsChromeOrXBL(JSContext* cx, JSObject* obj); + +} // namespace dom +} // namespace mozilla + +#endif diff --git a/js/xpconnect/tests/browser/browser.ini b/js/xpconnect/tests/browser/browser.ini new file mode 100644 index 000000000..bf7271df0 --- /dev/null +++ b/js/xpconnect/tests/browser/browser.ini @@ -0,0 +1,4 @@ +[DEFAULT] +support-files = + browser_deadObjectOnUnload.html +[browser_dead_object.js] diff --git a/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html b/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html new file mode 100644 index 000000000..ceb40b20b --- /dev/null +++ b/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html @@ -0,0 +1,18 @@ + + + + + + Test page for Bug 1242643 + + +

sample page

+ + + diff --git a/js/xpconnect/tests/browser/browser_dead_object.js b/js/xpconnect/tests/browser/browser_dead_object.js new file mode 100644 index 000000000..71ec36f2d --- /dev/null +++ b/js/xpconnect/tests/browser/browser_dead_object.js @@ -0,0 +1,21 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +// For bug 773980, test that Components.utils.isDeadWrapper works as expected. + +add_task(function* test() { + const url = "http://mochi.test:8888/browser/js/xpconnect/tests/browser/browser_deadObjectOnUnload.html"; + let newTab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url); + let browser = gBrowser.selectedBrowser; + let contentDocDead = yield ContentTask.spawn(browser,{}, function*(browser){ + let doc = content.document; + let promise = ContentTaskUtils.waitForEvent(this, "DOMContentLoaded", true); + content.location = "about:home"; + yield promise; + return Components.utils.isDeadWrapper(doc); + }); + is(contentDocDead, true, "wrapper is dead"); + yield BrowserTestUtils.removeTab(newTab); +}); diff --git a/js/xpconnect/tests/browser/moz.build b/js/xpconnect/tests/browser/moz.build new file mode 100644 index 000000000..29412da46 --- /dev/null +++ b/js/xpconnect/tests/browser/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +BROWSER_CHROME_MANIFESTS += ['browser.ini'] + diff --git a/js/xpconnect/tests/chrome/bug503926.xul b/js/xpconnect/tests/chrome/bug503926.xul new file mode 100644 index 000000000..ea5e02cbe --- /dev/null +++ b/js/xpconnect/tests/chrome/bug503926.xul @@ -0,0 +1,30 @@ + + + + + + + + Mozilla Bug 503926 + + + + + diff --git a/js/xpconnect/tests/chrome/chrome.ini b/js/xpconnect/tests/chrome/chrome.ini new file mode 100644 index 000000000..5a7b98214 --- /dev/null +++ b/js/xpconnect/tests/chrome/chrome.ini @@ -0,0 +1,119 @@ +[DEFAULT] +skip-if = os == 'android' +support-files = + bug503926.xul + file_bug618176.xul + file_bug996069.html + file_bug1050049.xml + file_bug1281071.html + file_discardSystemSource.html + file_evalInSandbox.html + file_expandosharing.jsm + outoflinexulscript.js + subscript.js + utf8_subscript.js + worker_discardSystemSource.js + !/js/xpconnect/tests/mochitest/bug500931_helper.html + !/js/xpconnect/tests/mochitest/bug571849_helper.html + !/js/xpconnect/tests/mochitest/chrome_wrappers_helper.html + !/js/xpconnect/tests/mochitest/file_bug706301.html + !/js/xpconnect/tests/mochitest/file_bug738244.html + !/js/xpconnect/tests/mochitest/file_bug760131.html + !/js/xpconnect/tests/mochitest/file_bug795275.html + !/js/xpconnect/tests/mochitest/file_bug795275.xml + !/js/xpconnect/tests/mochitest/file_bug799348.html + !/js/xpconnect/tests/mochitest/file_bug860494.html + !/js/xpconnect/tests/mochitest/file_documentdomain.html + !/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html + !/js/xpconnect/tests/mochitest/file_empty.html + !/js/xpconnect/tests/mochitest/file_exnstack.html + !/js/xpconnect/tests/mochitest/file_expandosharing.html + !/js/xpconnect/tests/mochitest/file_nodelists.html + !/js/xpconnect/tests/mochitest/file_evalInSandbox.html + +[test_APIExposer.xul] +[test_bug361111.xul] +[test_bug448587.xul] +[test_bug484459.xul] +skip-if = os == 'win' || os == 'mac' # bug 1131110 +[test_bug500931.xul] +[test_bug503926.xul] +[test_bug533596.xul] +[test_bug571849.xul] +[test_bug596580.xul] +[test_bug601803.xul] +[test_bug610390.xul] +[test_bug614757.xul] +[test_bug616992.xul] +[test_bug618176.xul] +[test_bug654370.xul] +[test_bug658560.xul] +[test_bug658909.xul] +[test_bug664689.xul] +[test_bug679861.xul] +[test_bug706301.xul] +[test_bug726949.xul] +[test_bug732665.xul] +[test_bug738244.xul] +[test_bug743843.xul] +[test_bug760076.xul] +[test_bug760131.html] +[test_bug763343.xul] +[test_bug771429.xul] +[test_bug773962.xul] +[test_bug792280.xul] +[test_bug793433.xul] +[test_bug795275.xul] +[test_bug799348.xul] +[test_bug801241.xul] +[test_bug812415.xul] +[test_bug853283.xul] +[test_bug853571.xul] +[test_bug858101.xul] +[test_bug860494.xul] +[test_bug865948.xul] +[test_bug866823.xul] +[test_bug895340.xul] +[test_bug932906.xul] +[test_bug996069.xul] +[test_bug1041626.xul] +[test_bug1042436.xul] +[test_bug1050049.html] +[test_bug1065185.html] +[test_bug1074863.html] +[test_bug1092477.xul] +[test_bug1124898.html] +[test_bug1126911.html] +[test_bug1281071.xul] +[test_chrometoSource.xul] +[test_cloneInto.xul] +[test_cows.xul] +[test_discardSystemSource.xul] +[test_documentdomain.xul] +[test_doublewrappedcompartments.xul] +[test_evalInSandbox.xul] +[test_evalInWindow.xul] +[test_exnstack.xul] +[test_expandosharing.xul] +[test_exposeInDerived.xul] +[test_getweakmapkeys.xul] +[test_localstorage_with_nsEp.xul] +[test_matches.xul] +[test_nodelists.xul] +[test_nsScriptErrorWithStack.html] +[test_onGarbageCollection.html] +[test_paris_weakmap_keys.xul] +[test_precisegc.xul] +[test_sandboxImport.xul] +[test_scriptSettings.xul] +[test_watchpoints.xul] +[test_weakmap_keys_preserved.xul] +[test_weakmap_keys_preserved2.xul] +[test_weakmaps.xul] +[test_weakref.xul] +[test_windowProxyDeadWrapper.html] +[test_wrappers.xul] +[test_wrappers-2.xul] +# Disabled until this test gets updated to test the new proxy based wrappers. +skip-if = true +[test_xrayToJS.xul] diff --git a/js/xpconnect/tests/chrome/file_bug1050049.xml b/js/xpconnect/tests/chrome/file_bug1050049.xml new file mode 100644 index 000000000..cc8653ea6 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug1050049.xml @@ -0,0 +1,10 @@ + + + + Anonymous Paragraph + + + Anonymous Paragraph + + diff --git a/js/xpconnect/tests/chrome/file_bug1281071.html b/js/xpconnect/tests/chrome/file_bug1281071.html new file mode 100644 index 000000000..2398ce4a5 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug1281071.html @@ -0,0 +1,13 @@ + + + diff --git a/js/xpconnect/tests/chrome/file_bug618176.xul b/js/xpconnect/tests/chrome/file_bug618176.xul new file mode 100644 index 000000000..f95352ae1 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug618176.xul @@ -0,0 +1,44 @@ + + + + + + diff --git a/js/xpconnect/tests/chrome/file_bug996069.html b/js/xpconnect/tests/chrome/file_bug996069.html new file mode 100644 index 000000000..e4bed0780 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_bug996069.html @@ -0,0 +1,11 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/file_discardSystemSource.html b/js/xpconnect/tests/chrome/file_discardSystemSource.html new file mode 100644 index 000000000..9f264ffe5 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_discardSystemSource.html @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/js/xpconnect/tests/chrome/file_evalInSandbox.html b/js/xpconnect/tests/chrome/file_evalInSandbox.html new file mode 100644 index 000000000..fb58f2bb4 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_evalInSandbox.html @@ -0,0 +1 @@ + diff --git a/js/xpconnect/tests/chrome/file_expandosharing.jsm b/js/xpconnect/tests/chrome/file_expandosharing.jsm new file mode 100644 index 000000000..2e7ffe541 --- /dev/null +++ b/js/xpconnect/tests/chrome/file_expandosharing.jsm @@ -0,0 +1,10 @@ +this.EXPORTED_SYMBOLS = ['checkFromJSM']; + +this.checkFromJSM = function checkFromJSM(target, is_op) { + is_op(target.numProp, 42, "Number expando works"); + is_op(target.strProp, "foo", "String expando works"); + // If is_op is todo_is, target.objProp will be undefined. + try { + is_op(target.objProp.bar, "baz", "Object expando works"); + } catch(e) { is_op(0, 1, "No object expando"); } +} diff --git a/js/xpconnect/tests/chrome/moz.build b/js/xpconnect/tests/chrome/moz.build new file mode 100644 index 000000000..7e310c9ea --- /dev/null +++ b/js/xpconnect/tests/chrome/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_CHROME_MANIFESTS += ['chrome.ini'] + +TEST_HARNESS_FILES.testing.mochitest.tests.js.xpconnect.tests.chrome += [ + 'file_discardSystemSource.html', + 'worker_discardSystemSource.js', +] diff --git a/js/xpconnect/tests/chrome/outoflinexulscript.js b/js/xpconnect/tests/chrome/outoflinexulscript.js new file mode 100644 index 000000000..a06b633af --- /dev/null +++ b/js/xpconnect/tests/chrome/outoflinexulscript.js @@ -0,0 +1,6 @@ +// Some unicode characters that must be decoded: +// ……………………………………………………………………………………………………………………………… +function outoflinefunction() { + return 42; +} + diff --git a/js/xpconnect/tests/chrome/subscript.js b/js/xpconnect/tests/chrome/subscript.js new file mode 100644 index 000000000..3065694b9 --- /dev/null +++ b/js/xpconnect/tests/chrome/subscript.js @@ -0,0 +1,6 @@ +const Cu = Components.utils; +Cu.import("resource://gre/modules/Services.jsm"); + +var ns = {}; +Services.scriptloader.loadSubScript("resource://gre/modules/NetUtil.jsm", ns); +var NetUtil = ns.NetUtil; diff --git a/js/xpconnect/tests/chrome/test_APIExposer.xul b/js/xpconnect/tests/chrome/test_APIExposer.xul new file mode 100644 index 000000000..d89d19759 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_APIExposer.xul @@ -0,0 +1,51 @@ + + + + + + + + + + Mozilla Bug 634156 + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug1041626.xul b/js/xpconnect/tests/chrome/test_bug1041626.xul new file mode 100644 index 000000000..c7c7b7024 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1041626.xul @@ -0,0 +1,64 @@ + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug1065185.html b/js/xpconnect/tests/chrome/test_bug1065185.html new file mode 100644 index 000000000..cdd65326f --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug1065185.html @@ -0,0 +1,64 @@ + + + + + + Test for Bug 1065185 + + + + + + +Mozilla Bug 1065185 +

+ +
+
+ + + +
diff --git a/js/xpconnect/tests/chrome/test_bug361111.xul b/js/xpconnect/tests/chrome/test_bug361111.xul new file mode 100644 index 000000000..4b790ea2d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug361111.xul @@ -0,0 +1,33 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug448587.xul b/js/xpconnect/tests/chrome/test_bug448587.xul new file mode 100644 index 000000000..98ee5deda --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug448587.xul @@ -0,0 +1,37 @@ + + + + + + + + + + Mozilla Bug 448587 + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug484459.xul b/js/xpconnect/tests/chrome/test_bug484459.xul new file mode 100644 index 000000000..621a8ae1d --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug484459.xul @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + ", + "contents of w.location are correct"); + is(Components.utils.evalInSandbox("x * 4", sandbox), 12, + "Unexpected return from the sandbox"); + SimpleTest.finish(); + } + ]]> + diff --git a/js/xpconnect/tests/chrome/test_bug500931.xul b/js/xpconnect/tests/chrome/test_bug500931.xul new file mode 100644 index 000000000..28d51e362 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug500931.xul @@ -0,0 +1,41 @@ + + + + + + + + + + Mozilla Bug 500931 + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug503926.xul b/js/xpconnect/tests/chrome/test_bug503926.xul new file mode 100644 index 000000000..ab611e489 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug503926.xul @@ -0,0 +1,59 @@ + + + + + + + + + + Mozilla Bug 503926 + + + diff --git a/js/xpconnect/tests/chrome/test_bug571849.xul b/js/xpconnect/tests/chrome/test_bug571849.xul new file mode 100644 index 000000000..a5750b6d4 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug571849.xul @@ -0,0 +1,44 @@ + + + + + + + + + + Mozilla Bug 500931 + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug596580.xul b/js/xpconnect/tests/chrome/test_bug596580.xul new file mode 100644 index 000000000..fab7058ac --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug596580.xul @@ -0,0 +1,48 @@ + + + + + + + + + Mozilla Bug 596580 + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug601803.xul b/js/xpconnect/tests/chrome/test_bug601803.xul new file mode 100644 index 000000000..89c43f024 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug601803.xul @@ -0,0 +1,38 @@ + + + + + + + + + + Mozilla Bug 601803 + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug614757.xul b/js/xpconnect/tests/chrome/test_bug614757.xul new file mode 100644 index 000000000..c9a50be33 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug614757.xul @@ -0,0 +1,34 @@ + + + + + + + + + + Mozilla Bug 614757 + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug743843.xul b/js/xpconnect/tests/chrome/test_bug743843.xul new file mode 100644 index 000000000..df8df320b --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug743843.xul @@ -0,0 +1,39 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug760076.xul b/js/xpconnect/tests/chrome/test_bug760076.xul new file mode 100644 index 000000000..a0eb4904c --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug760076.xul @@ -0,0 +1,49 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug760131.html b/js/xpconnect/tests/chrome/test_bug760131.html new file mode 100644 index 000000000..ee02a96eb --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug760131.html @@ -0,0 +1,44 @@ + + + + + + Test for Bug 760131 + + + + +Mozilla Bug 760131 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/chrome/test_bug763343.xul b/js/xpconnect/tests/chrome/test_bug763343.xul new file mode 100644 index 000000000..1519e1fbe --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug763343.xul @@ -0,0 +1,39 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug771429.xul b/js/xpconnect/tests/chrome/test_bug771429.xul new file mode 100644 index 000000000..c6f14ee74 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug771429.xul @@ -0,0 +1,51 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_bug773962.xul b/js/xpconnect/tests/chrome/test_bug773962.xul new file mode 100644 index 000000000..76a9b0713 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_bug773962.xul @@ -0,0 +1,82 @@ + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_documentdomain.xul b/js/xpconnect/tests/chrome/test_documentdomain.xul new file mode 100644 index 000000000..858d2c4f0 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_documentdomain.xul @@ -0,0 +1,101 @@ + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_evalInSandbox.xul b/js/xpconnect/tests/chrome/test_evalInSandbox.xul new file mode 100644 index 000000000..dc5e96998 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_evalInSandbox.xul @@ -0,0 +1,202 @@ + + + + + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_evalInWindow.xul b/js/xpconnect/tests/chrome/test_evalInWindow.xul new file mode 100644 index 000000000..6c69af05c --- /dev/null +++ b/js/xpconnect/tests/chrome/test_evalInWindow.xul @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_exnstack.xul b/js/xpconnect/tests/chrome/test_exnstack.xul new file mode 100644 index 000000000..8536cf8dc --- /dev/null +++ b/js/xpconnect/tests/chrome/test_exnstack.xul @@ -0,0 +1,68 @@ + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_expandosharing.xul b/js/xpconnect/tests/chrome/test_expandosharing.xul new file mode 100644 index 000000000..93884a82f --- /dev/null +++ b/js/xpconnect/tests/chrome/test_expandosharing.xul @@ -0,0 +1,145 @@ + + + + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_nodelists.xul b/js/xpconnect/tests/chrome/test_nodelists.xul new file mode 100644 index 000000000..7b91d7887 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_nodelists.xul @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_watchpoints.xul b/js/xpconnect/tests/chrome/test_watchpoints.xul new file mode 100644 index 000000000..2262b1a90 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_watchpoints.xul @@ -0,0 +1,75 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xul b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xul new file mode 100644 index 000000000..95908a7b1 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved.xul @@ -0,0 +1,37 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xul b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xul new file mode 100644 index 000000000..c6931af36 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakmap_keys_preserved2.xul @@ -0,0 +1,84 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_weakmaps.xul b/js/xpconnect/tests/chrome/test_weakmaps.xul new file mode 100644 index 000000000..e741a41c6 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakmaps.xul @@ -0,0 +1,272 @@ + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_weakref.xul b/js/xpconnect/tests/chrome/test_weakref.xul new file mode 100644 index 000000000..3950a0e35 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_weakref.xul @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html b/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html new file mode 100644 index 000000000..91f4037f7 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html @@ -0,0 +1,72 @@ + + + + + + Test for Bug 1223372 + + + + + + +Mozilla Bug 1223372 + + + + + diff --git a/js/xpconnect/tests/chrome/test_wrappers-2.xul b/js/xpconnect/tests/chrome/test_wrappers-2.xul new file mode 100644 index 000000000..abab47f80 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_wrappers-2.xul @@ -0,0 +1,215 @@ + + + + + + + + + + Mozilla Bug 403005 + Mozilla Bug 409298 + + + + + + diff --git a/js/xpconnect/tests/chrome/test_wrappers.xul b/js/xpconnect/tests/chrome/test_wrappers.xul new file mode 100644 index 000000000..41504d28f --- /dev/null +++ b/js/xpconnect/tests/chrome/test_wrappers.xul @@ -0,0 +1,96 @@ + + + + + + + + + + Mozilla Bug 533596 + + + + + + diff --git a/js/xpconnect/tests/chrome/test_xrayToJS.xul b/js/xpconnect/tests/chrome/test_xrayToJS.xul new file mode 100644 index 000000000..2f4e70f47 --- /dev/null +++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul @@ -0,0 +1,948 @@ + + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file2_bug629227.html b/js/xpconnect/tests/mochitest/file2_bug629227.html new file mode 100644 index 000000000..02a054086 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file2_bug629227.html @@ -0,0 +1,11 @@ + + + + + + +
+ + diff --git a/js/xpconnect/tests/mochitest/file_bug505915.html b/js/xpconnect/tests/mochitest/file_bug505915.html new file mode 100644 index 000000000..512912691 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug505915.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug650273.html b/js/xpconnect/tests/mochitest/file_bug650273.html new file mode 100644 index 000000000..5fe33e969 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug650273.html @@ -0,0 +1,31 @@ + + + diff --git a/js/xpconnect/tests/mochitest/file_bug658560.html b/js/xpconnect/tests/mochitest/file_bug658560.html new file mode 100644 index 000000000..411d31ac7 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug658560.html @@ -0,0 +1,4 @@ + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug706301.html b/js/xpconnect/tests/mochitest/file_bug706301.html new file mode 100644 index 000000000..805449b4a --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug706301.html @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug720619.html b/js/xpconnect/tests/mochitest/file_bug720619.html new file mode 100644 index 000000000..d198ba1fa --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug720619.html @@ -0,0 +1,10 @@ + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug738244.html b/js/xpconnect/tests/mochitest/file_bug738244.html new file mode 100644 index 000000000..a399d9f0e --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug738244.html @@ -0,0 +1,10 @@ + + +
+ + +
+ + + diff --git a/js/xpconnect/tests/mochitest/file_bug760131.html b/js/xpconnect/tests/mochitest/file_bug760131.html new file mode 100644 index 000000000..736732a0a --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug760131.html @@ -0,0 +1,23 @@ + +
+ + + + diff --git a/js/xpconnect/tests/mochitest/file_bug781476.html b/js/xpconnect/tests/mochitest/file_bug781476.html new file mode 100644 index 000000000..745f8818e --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug781476.html @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug789713.html b/js/xpconnect/tests/mochitest/file_bug789713.html new file mode 100644 index 000000000..ea21dae6c --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug789713.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug795275.html b/js/xpconnect/tests/mochitest/file_bug795275.html new file mode 100644 index 000000000..95e9f2a8e --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug795275.html @@ -0,0 +1,28 @@ + + + + + + +
+ + diff --git a/js/xpconnect/tests/mochitest/file_bug795275.xml b/js/xpconnect/tests/mochitest/file_bug795275.xml new file mode 100644 index 000000000..f9e5e60f5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug795275.xml @@ -0,0 +1,19 @@ + + + + + + + Components.interfaces; + + + + + var foo = Components; + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug799348.html b/js/xpconnect/tests/mochitest/file_bug799348.html new file mode 100644 index 000000000..5800868db --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug799348.html @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug802557.html b/js/xpconnect/tests/mochitest/file_bug802557.html new file mode 100644 index 000000000..39f952bc5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug802557.html @@ -0,0 +1,62 @@ + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_bug860494.html b/js/xpconnect/tests/mochitest/file_bug860494.html new file mode 100644 index 000000000..63a700379 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_bug860494.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_crossOriginObjects.html b/js/xpconnect/tests/mochitest/file_crossOriginObjects.html new file mode 100644 index 000000000..c3093ebda --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_crossOriginObjects.html @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_crossOriginObjects_documentDomain.html b/js/xpconnect/tests/mochitest/file_crossOriginObjects_documentDomain.html new file mode 100644 index 000000000..1c0f05bd2 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_crossOriginObjects_documentDomain.html @@ -0,0 +1,55 @@ + + + + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html b/js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html new file mode 100644 index 000000000..b25cdb2f9 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_crosscompartment_weakmap.html @@ -0,0 +1,9 @@ + + + + + Test Cross-Compartment DOM WeakMaps + + + + diff --git a/js/xpconnect/tests/mochitest/file_documentdomain.html b/js/xpconnect/tests/mochitest/file_documentdomain.html new file mode 100644 index 000000000..784ed269d --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_documentdomain.html @@ -0,0 +1,41 @@ + + + + + + +Better Late than Never + + diff --git a/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html b/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html new file mode 100644 index 000000000..f789a33d7 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_doublewrappedcompartments.html @@ -0,0 +1,10 @@ + + + + diff --git a/js/xpconnect/tests/mochitest/file_empty.html b/js/xpconnect/tests/mochitest/file_empty.html new file mode 100644 index 000000000..ebe8e56a6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_empty.html @@ -0,0 +1,3 @@ + + +empty test pageNothing to see here diff --git a/js/xpconnect/tests/mochitest/file_evalInSandbox.html b/js/xpconnect/tests/mochitest/file_evalInSandbox.html new file mode 100644 index 000000000..f53aa1166 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_evalInSandbox.html @@ -0,0 +1,8 @@ + + + + + diff --git a/js/xpconnect/tests/mochitest/file_exnstack.html b/js/xpconnect/tests/mochitest/file_exnstack.html new file mode 100644 index 000000000..448e3c0a7 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_exnstack.html @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/file_expandosharing.html b/js/xpconnect/tests/mochitest/file_expandosharing.html new file mode 100644 index 000000000..ceb4131bb --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_expandosharing.html @@ -0,0 +1,34 @@ + + + + + + + Salut, Ma Cherise. ;-) + + diff --git a/js/xpconnect/tests/mochitest/file_matches.html b/js/xpconnect/tests/mochitest/file_matches.html new file mode 100644 index 000000000..0dc101b53 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_matches.html @@ -0,0 +1 @@ + diff --git a/js/xpconnect/tests/mochitest/file_nodelists.html b/js/xpconnect/tests/mochitest/file_nodelists.html new file mode 100644 index 000000000..2195c62cc --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_nodelists.html @@ -0,0 +1,7 @@ + + +

+

+

+ + diff --git a/js/xpconnect/tests/mochitest/file_wrappers-2.html b/js/xpconnect/tests/mochitest/file_wrappers-2.html new file mode 100644 index 000000000..e27b07ed6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/file_wrappers-2.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/js/xpconnect/tests/mochitest/inner.html b/js/xpconnect/tests/mochitest/inner.html new file mode 100644 index 000000000..8021a5553 --- /dev/null +++ b/js/xpconnect/tests/mochitest/inner.html @@ -0,0 +1,7 @@ + + + Inner frame for bug 39685 mochitest + + + + diff --git a/js/xpconnect/tests/mochitest/mochitest.ini b/js/xpconnect/tests/mochitest/mochitest.ini new file mode 100644 index 000000000..de6759e7a --- /dev/null +++ b/js/xpconnect/tests/mochitest/mochitest.ini @@ -0,0 +1,107 @@ +[DEFAULT] +support-files = + bug500931_helper.html + bug504877_helper.html + bug571849_helper.html + bug589028_helper.html + bug92773_helper.html + chrome_wrappers_helper.html + file1_bug629227.html + file2_bug629227.html + file_bug505915.html + file_bug650273.html + file_bug658560.html + file_bug706301.html + file_bug720619.html + file_bug738244.html + file_bug760131.html + file_bug781476.html + file_bug789713.html + file_bug795275.html + file_bug795275.xml + file_bug799348.html + file_bug802557.html + file_bug860494.html + file_crossOriginObjects.html + file_crossOriginObjects_documentDomain.html + file_crosscompartment_weakmap.html + file_documentdomain.html + file_doublewrappedcompartments.html + file_empty.html + file_evalInSandbox.html + file_exnstack.html + file_expandosharing.html + file_matches.html + file_nodelists.html + file_wrappers-2.html + inner.html + test1_bug629331.html + test2_bug629331.html + +[test_bug384632.html] +[test_bug390488.html] +[test_bug393269.html] +[test_bug396851.html] +[test_bug428021.html] +[test_bug446584.html] +[test_bug462428.html] +[test_bug478438.html] +[test_bug500691.html] +[test_bug504877.html] +[test_bug505915.html] +[test_bug560351.html] +[test_bug585745.html] +[test_bug589028.html] +[test_bug601299.html] +[test_bug605167.html] +[test_bug618017.html] +[test_bug623437.html] +[test_bug628410.html] +[test_bug628794.html] +[test_bug629227.html] +[test_bug629331.html] +[test_bug636097.html] +[test_bug650273.html] +[test_bug655297-1.html] +[test_bug655297-2.html] +[test_bug661980.html] +[test_bug691059.html] +[test_bug720619.html] +[test_bug731471.html] +[test_bug764389.html] +[test_bug772288.html] +[test_bug781476.html] +[test_bug789713.html] +[test_bug790732.html] +[test_bug793969.html] +[test_bug800864.html] +[test_bug802557.html] +[test_bug803730.html] +[test_bug809547.html] +[test_bug829872.html] +[test_bug862380.html] +[test_bug865260.html] +[test_bug870423.html] +[test_bug871887.html] +[test_bug912322.html] +[test_bug916945.html] +[test_bug92773.html] +[test_bug940783.html] +[test_bug965082.html] +[test_bug960820.html] +[test_bug986542.html] +[test_bug993423.html] +[test_bug1005806.html] +[test_bug1094930.html] +[test_bug1158558.html] +[test_crossOriginObjects.html] +[test_crosscompartment_weakmap.html] +[test_frameWrapping.html] +# The JS test component we use below is only available in debug builds. +[test_getWebIDLCaller.html] +skip-if = (debug == false || os == "android") +[test_nac.xhtml] +[test_sameOriginPolicy.html] +[test_sandbox_fetch.html] + support-files = + ../../../../dom/tests/mochitest/fetch/test_fetch_basic.js diff --git a/js/xpconnect/tests/mochitest/moz.build b/js/xpconnect/tests/mochitest/moz.build new file mode 100644 index 000000000..8e5cb5d71 --- /dev/null +++ b/js/xpconnect/tests/mochitest/moz.build @@ -0,0 +1,8 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +MOCHITEST_MANIFESTS += ['mochitest.ini'] + diff --git a/js/xpconnect/tests/mochitest/test1_bug629331.html b/js/xpconnect/tests/mochitest/test1_bug629331.html new file mode 100644 index 000000000..18843e08d --- /dev/null +++ b/js/xpconnect/tests/mochitest/test1_bug629331.html @@ -0,0 +1,19 @@ + + + + + diff --git a/js/xpconnect/tests/mochitest/test2_bug629331.html b/js/xpconnect/tests/mochitest/test2_bug629331.html new file mode 100644 index 000000000..1bcf03739 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test2_bug629331.html @@ -0,0 +1,18 @@ + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug1005806.html b/js/xpconnect/tests/mochitest/test_bug1005806.html new file mode 100644 index 000000000..4f041c4be --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1005806.html @@ -0,0 +1,27 @@ + + + + + + Test for Bug 1005806 + + + + +Mozilla Bug 1005806 +

+ +
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug1094930.html b/js/xpconnect/tests/mochitest/test_bug1094930.html new file mode 100644 index 000000000..434949360 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1094930.html @@ -0,0 +1,29 @@ + + + + + + Test for Bug 1094930 + + + + + +Mozilla Bug 1094930 +

+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug1158558.html b/js/xpconnect/tests/mochitest/test_bug1158558.html new file mode 100644 index 000000000..3d142e44a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug1158558.html @@ -0,0 +1,47 @@ + + + + + + Test for Bug 1158558 + + + + +Mozilla Bug 1158558 +

+ +
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug384632.html b/js/xpconnect/tests/mochitest/test_bug384632.html new file mode 100644 index 000000000..a5e02e448 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug384632.html @@ -0,0 +1,34 @@ + + + + + Test for Bug 384632 + + + + +Mozilla Bug 384632 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug390488.html b/js/xpconnect/tests/mochitest/test_bug390488.html new file mode 100644 index 000000000..18b4c141c --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug390488.html @@ -0,0 +1,64 @@ + + + + + Test for Bug 390488 + + + + +Mozilla Bug 390488 +

+

+

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug393269.html b/js/xpconnect/tests/mochitest/test_bug393269.html new file mode 100644 index 000000000..048698854 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug393269.html @@ -0,0 +1,46 @@ + + + + + Test for Bug 393269 + + + + +Mozilla Bug 393269 + +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug396851.html b/js/xpconnect/tests/mochitest/test_bug396851.html new file mode 100644 index 000000000..aa7a98c08 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug396851.html @@ -0,0 +1,41 @@ + + + + + Test for Bug 396851 + + + + + + +Mozilla Bug 396851 +

+ + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug428021.html b/js/xpconnect/tests/mochitest/test_bug428021.html new file mode 100644 index 000000000..75c9edfce --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug428021.html @@ -0,0 +1,40 @@ + + + + + Test for Bug 428021 + + + + +Mozilla Bug 428021 +

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug446584.html b/js/xpconnect/tests/mochitest/test_bug446584.html new file mode 100644 index 000000000..4ad1c1d3c --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug446584.html @@ -0,0 +1,47 @@ + + + + + Test for Bug 446584 + + + + +Mozilla Bug 446584 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug462428.html b/js/xpconnect/tests/mochitest/test_bug462428.html new file mode 100644 index 000000000..8655bc359 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug462428.html @@ -0,0 +1,51 @@ + + + + + Test for Bug 462428 + + + + +Mozilla Bug 462428 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug478438.html b/js/xpconnect/tests/mochitest/test_bug478438.html new file mode 100644 index 000000000..76faa706c --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug478438.html @@ -0,0 +1,65 @@ + + + + + Test for Bug 478438 + + + + + +Mozilla Bug 478438 +

+
+ +
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug484107.html b/js/xpconnect/tests/mochitest/test_bug484107.html new file mode 100644 index 000000000..3bb82947e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug484107.html @@ -0,0 +1,99 @@ + + + + + Test for Bug 484107 + + + + +Mozilla Bug 484107 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug500691.html b/js/xpconnect/tests/mochitest/test_bug500691.html new file mode 100644 index 000000000..d543e4c03 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug500691.html @@ -0,0 +1,27 @@ + + + + + Test for Bug 500691 + + + + +Mozilla Bug 500691 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug504877.html b/js/xpconnect/tests/mochitest/test_bug504877.html new file mode 100644 index 000000000..3b9fe9a52 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug504877.html @@ -0,0 +1,64 @@ + + + + + Test for Bug 504877 + + + + +Mozilla Bug 504877 +

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug505915.html b/js/xpconnect/tests/mochitest/test_bug505915.html new file mode 100644 index 000000000..04c5da82a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug505915.html @@ -0,0 +1,50 @@ + + + + + Test for Bug 505915 + + + + +Mozilla Bug 505915 +

+ +
+
+
+ + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug560351.html b/js/xpconnect/tests/mochitest/test_bug560351.html new file mode 100644 index 000000000..27c26335f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug560351.html @@ -0,0 +1,36 @@ + + + + + Test for Bug 560351 + + + + +Mozilla Bug 560351 +

+ +
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug585745.html b/js/xpconnect/tests/mochitest/test_bug585745.html new file mode 100644 index 000000000..a910bf376 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug585745.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 585745 + + + + +Mozilla Bug 585745 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug589028.html b/js/xpconnect/tests/mochitest/test_bug589028.html new file mode 100644 index 000000000..d25c9a901 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug589028.html @@ -0,0 +1,62 @@ + + + + + Test for Bug 589028 + + + + +Mozilla Bug 589028 +

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug601299.html b/js/xpconnect/tests/mochitest/test_bug601299.html new file mode 100644 index 000000000..375c858a6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug601299.html @@ -0,0 +1,18 @@ + + + + + Test for Bug 601299 + + + + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug605167.html b/js/xpconnect/tests/mochitest/test_bug605167.html new file mode 100644 index 000000000..eaa4d2f67 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug605167.html @@ -0,0 +1,56 @@ + + + + + Test for Bug 505915 + + + + +Mozilla Bug 505915 +

+ +
+
+
+ + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug618017.html b/js/xpconnect/tests/mochitest/test_bug618017.html new file mode 100644 index 000000000..c5b815e8d --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug618017.html @@ -0,0 +1,28 @@ + + + + + Test for Bug 618017 + + + + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug623437.html b/js/xpconnect/tests/mochitest/test_bug623437.html new file mode 100644 index 000000000..c925932a5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug623437.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 623437 + + + + +Mozilla Bug 623437 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug628410.html b/js/xpconnect/tests/mochitest/test_bug628410.html new file mode 100644 index 000000000..1708ae393 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug628410.html @@ -0,0 +1,31 @@ + + + + + Test for Bug 628410 + + + + +Mozilla Bug 628410 +

+ + +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug628794.html b/js/xpconnect/tests/mochitest/test_bug628794.html new file mode 100644 index 000000000..115b6118f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug628794.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 585745 + + + + +Mozilla Bug 585745 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug629227.html b/js/xpconnect/tests/mochitest/test_bug629227.html new file mode 100644 index 000000000..0ed381285 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug629227.html @@ -0,0 +1,47 @@ + + + + + Test for Bug 629227 + + + + +Mozilla Bug 629227 +

+ +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug629331.html b/js/xpconnect/tests/mochitest/test_bug629331.html new file mode 100644 index 000000000..413ec9d7b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug629331.html @@ -0,0 +1,37 @@ + + + + + Test for Bug 629331 + + + + +Mozilla Bug 629331 +

+ +
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug636097.html b/js/xpconnect/tests/mochitest/test_bug636097.html new file mode 100644 index 000000000..09fd695a8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug636097.html @@ -0,0 +1,62 @@ + + + + + Test for Bug 504877 + + + + +Mozilla Bug 504877 +

+ +
+
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug650273.html b/js/xpconnect/tests/mochitest/test_bug650273.html new file mode 100644 index 000000000..37f6609d8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug650273.html @@ -0,0 +1,42 @@ + + + + + Test for Bug 650273 + + + + +Mozilla Bug 650273 +

+ +
+
+
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug655297-1.html b/js/xpconnect/tests/mochitest/test_bug655297-1.html new file mode 100644 index 000000000..8f615762f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug655297-1.html @@ -0,0 +1,49 @@ + + + + + Test for Bug 655297 + + + + +Mozilla Bug 655297 +

+ +
0
1
2
3
4
+
5
6
7
8
9
+
+
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug655297-2.html b/js/xpconnect/tests/mochitest/test_bug655297-2.html new file mode 100644 index 000000000..2a99557d2 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug655297-2.html @@ -0,0 +1,49 @@ + + + + + Test for Bug 655297 + + + + +Mozilla Bug 655297 +

+ +

0

1

2

3

4

+

5

6

7

8

9

+
+
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug661980.html b/js/xpconnect/tests/mochitest/test_bug661980.html new file mode 100644 index 000000000..e372c1ee0 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug661980.html @@ -0,0 +1,61 @@ + + + + + Test for Bug 661980 + + + + +Mozilla Bug 661980 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug691059.html b/js/xpconnect/tests/mochitest/test_bug691059.html new file mode 100644 index 000000000..830cf7b5d --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug691059.html @@ -0,0 +1,59 @@ + + + + + Test for Bug 691059 + + + + +Mozilla Bug 691059 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug720619.html b/js/xpconnect/tests/mochitest/test_bug720619.html new file mode 100644 index 000000000..bf973a30a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug720619.html @@ -0,0 +1,55 @@ + + + + + Test for Bug 629227 + + + + +Mozilla Bug 720619 +

+ +

+ +
+
+
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug731471.html b/js/xpconnect/tests/mochitest/test_bug731471.html new file mode 100644 index 000000000..02f50d401 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug731471.html @@ -0,0 +1,42 @@ + + + + + + Test for Bug 731471 + + + + +Mozilla Bug 731471 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug764389.html b/js/xpconnect/tests/mochitest/test_bug764389.html new file mode 100644 index 000000000..3af46c51b --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug764389.html @@ -0,0 +1,40 @@ + + + + + + Test for Bug 764389 + + + + +Mozilla Bug 764389 +

+ +
+
+
+
+
diff --git a/js/xpconnect/tests/mochitest/test_bug789713.html b/js/xpconnect/tests/mochitest/test_bug789713.html
new file mode 100644
index 000000000..58ebf2fb2
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/test_bug789713.html
@@ -0,0 +1,39 @@
+
+
+
+
+  
+  Test for Bug 789713
+  
+  
+
+
+Mozilla Bug 789713
+

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug790732.html b/js/xpconnect/tests/mochitest/test_bug790732.html new file mode 100644 index 000000000..48b7fdbb6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug790732.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 790732 + + + + + +Mozilla Bug 790732 +

+ +
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug793969.html b/js/xpconnect/tests/mochitest/test_bug793969.html new file mode 100644 index 000000000..0936967bd --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug793969.html @@ -0,0 +1,53 @@ + + + + + + Test for Bug 793969 + + + + +Mozilla Bug 793969 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug800864.html b/js/xpconnect/tests/mochitest/test_bug800864.html new file mode 100644 index 000000000..560dda072 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug800864.html @@ -0,0 +1,51 @@ + + + + + Test for Bug 800864 + + + + +Mozilla Bug 800864 +

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_bug802557.html b/js/xpconnect/tests/mochitest/test_bug802557.html new file mode 100644 index 000000000..073f22e72 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug802557.html @@ -0,0 +1,116 @@ + + + + + + Test for Bug 802557 + + + + + +Mozilla Bug 802557 +

+ + +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug803730.html b/js/xpconnect/tests/mochitest/test_bug803730.html new file mode 100644 index 000000000..4a1552f4f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug803730.html @@ -0,0 +1,41 @@ + + + + + + Test for Bug 803730 + + + + +Mozilla Bug 803730 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug809547.html b/js/xpconnect/tests/mochitest/test_bug809547.html new file mode 100644 index 000000000..c2c87623f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug809547.html @@ -0,0 +1,42 @@ + + + + + + Test for Bug 809547 + + + + +Mozilla Bug 809547 +

+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug829872.html b/js/xpconnect/tests/mochitest/test_bug829872.html new file mode 100644 index 000000000..9c48983e8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug829872.html @@ -0,0 +1,52 @@ + + + + + + Test for Bug 829872 + + + + + +Mozilla Bug 829872 +

+ +
+
+ + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug862380.html b/js/xpconnect/tests/mochitest/test_bug862380.html new file mode 100644 index 000000000..0d3fb7329 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug862380.html @@ -0,0 +1,43 @@ + + + + + + Test for Bug 862380 + + + + + +Mozilla Bug 862380 +

+ + +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug865260.html b/js/xpconnect/tests/mochitest/test_bug865260.html new file mode 100644 index 000000000..8878e9df5 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug865260.html @@ -0,0 +1,33 @@ + + + + + + Test for Bug 865260 + + + + + +Mozilla Bug 865260 +

+
+ +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug870423.html b/js/xpconnect/tests/mochitest/test_bug870423.html new file mode 100644 index 000000000..6fce43527 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug870423.html @@ -0,0 +1,51 @@ + + + + + + Test for Bug 870423 + + + + + +Mozilla Bug 870423 +

+ + + +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug871887.html b/js/xpconnect/tests/mochitest/test_bug871887.html new file mode 100644 index 000000000..89239eb44 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug871887.html @@ -0,0 +1,43 @@ + + + + + + Test for Bug 871887 + + + + + +Mozilla Bug 871887 +

+
+Watch the Llama +
+
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug912322.html b/js/xpconnect/tests/mochitest/test_bug912322.html new file mode 100644 index 000000000..c274b6afc --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug912322.html @@ -0,0 +1,36 @@ + + + + + + Test for Bug 912322 + + + + + +Mozilla Bug 912322 +

+ +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug916945.html b/js/xpconnect/tests/mochitest/test_bug916945.html new file mode 100644 index 000000000..078f2a445 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug916945.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 916945 + + + + + +Mozilla Bug 916945 +

+ + + +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug92773.html b/js/xpconnect/tests/mochitest/test_bug92773.html new file mode 100644 index 000000000..4cec5eae6 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug92773.html @@ -0,0 +1,43 @@ + + + + + Test for Bug 92773 + + + + +Mozilla Bug 92773 +

+ + +
+
+
+ + + + + diff --git a/js/xpconnect/tests/mochitest/test_bug940783.html b/js/xpconnect/tests/mochitest/test_bug940783.html new file mode 100644 index 000000000..9e9e49084 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug940783.html @@ -0,0 +1,62 @@ + + + + + + Test for Bug 940783 + + + + + +Mozilla Bug 940783 +

+ +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug960820.html b/js/xpconnect/tests/mochitest/test_bug960820.html new file mode 100644 index 000000000..43310f589 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug960820.html @@ -0,0 +1,56 @@ + + + + + + Test for Bug 960820 + + + + + +Mozilla Bug 960820 +

+ +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug965082.html b/js/xpconnect/tests/mochitest/test_bug965082.html new file mode 100644 index 000000000..8f04cd3f8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug965082.html @@ -0,0 +1,39 @@ + + + + + + Test for Bug 965082 + + + + + +Mozilla Bug 965082 +

+ + +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug986542.html b/js/xpconnect/tests/mochitest/test_bug986542.html new file mode 100644 index 000000000..a0759c43f --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug986542.html @@ -0,0 +1,45 @@ + + + + + + Test for Bug 986542 + + + + + +Mozilla Bug 986542 +

+ +
+ +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_bug993423.html b/js/xpconnect/tests/mochitest/test_bug993423.html new file mode 100644 index 000000000..43c17a49a --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_bug993423.html @@ -0,0 +1,47 @@ + + + + + + Test for Bug 993423 + + + + + +Mozilla Bug 993423 +

+ +
+
+ + + + + + + + + + diff --git a/js/xpconnect/tests/mochitest/test_crossOriginObjects.html b/js/xpconnect/tests/mochitest/test_crossOriginObjects.html new file mode 100644 index 000000000..adfd25869 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_crossOriginObjects.html @@ -0,0 +1,336 @@ + + + +Cross-origin behavior of Window and Location + + + + + +
+ + + diff --git a/js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html b/js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html new file mode 100644 index 000000000..e50b1f1bd --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_crosscompartment_weakmap.html @@ -0,0 +1,44 @@ + + + + Test Cross-Compartment DOM WeakMaps + + + + +

+ + + + +
+ + diff --git a/js/xpconnect/tests/mochitest/test_frameWrapping.html b/js/xpconnect/tests/mochitest/test_frameWrapping.html new file mode 100644 index 000000000..7aecf1c3e --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_frameWrapping.html @@ -0,0 +1,37 @@ + + + + + Test for Bug + + + + +Mozilla Bug +

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_getWebIDLCaller.html b/js/xpconnect/tests/mochitest/test_getWebIDLCaller.html new file mode 100644 index 000000000..0627eb659 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_getWebIDLCaller.html @@ -0,0 +1,49 @@ + + + + + + Test for Bug 968335 + + + + + +Mozilla Bug 968335 +

+ +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_nac.xhtml b/js/xpconnect/tests/mochitest/test_nac.xhtml new file mode 100644 index 000000000..0f18fe489 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_nac.xhtml @@ -0,0 +1,64 @@ + + + + Test for Bug 914618 + + + + + + hidden text + + + var win = XPCNativeWrapper.unwrap(window); + var nac = document.getAnonymousNodes(this)[0].firstChild; + win.is(nac.textContent, "hidden text", "XBL can see NAC"); + win.playWithNAC(nac); + + + + + + + +Mozilla Bug 914618 +

+ +
+ +
+
+ + diff --git a/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html b/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html new file mode 100644 index 000000000..1969e517c --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_sameOriginPolicy.html @@ -0,0 +1,109 @@ + + + + + + Test for Bug 801576 + + + + +Mozilla Bug 801576 +

+ +
+
+
+ + + diff --git a/js/xpconnect/tests/mochitest/test_sandbox_fetch.html b/js/xpconnect/tests/mochitest/test_sandbox_fetch.html new file mode 100644 index 000000000..b206efdb8 --- /dev/null +++ b/js/xpconnect/tests/mochitest/test_sandbox_fetch.html @@ -0,0 +1,54 @@ + + + + Fetch in JS Sandbox + + + + + + + + diff --git a/js/xpconnect/tests/moz.build b/js/xpconnect/tests/moz.build new file mode 100644 index 000000000..f767c210d --- /dev/null +++ b/js/xpconnect/tests/moz.build @@ -0,0 +1,31 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +TEST_DIRS += [ + 'mochitest', + 'chrome', + 'browser', + 'components/native', +] + +if CONFIG['COMPILE_ENVIRONMENT']: + TEST_DIRS += [ + 'idl', + ] + +XPCSHELL_TESTS_MANIFESTS += [ + 'unit/xpcshell.ini', +] + +TEST_HARNESS_FILES.xpcshell.js.xpconnect.tests.components.js += [ + 'components/js/xpctest.manifest', + 'components/js/xpctest_attributes.js', + 'components/js/xpctest_bug809674.js', + 'components/js/xpctest_interfaces.js', + 'components/js/xpctest_params.js', + 'components/js/xpctest_returncode_child.js', + 'components/js/xpctest_utils.js', +] diff --git a/js/xpconnect/tests/unit/CatRegistrationComponents.manifest b/js/xpconnect/tests/unit/CatRegistrationComponents.manifest new file mode 100644 index 000000000..11646b028 --- /dev/null +++ b/js/xpconnect/tests/unit/CatRegistrationComponents.manifest @@ -0,0 +1,2 @@ +category test-cat CatRegisteredComponent @unit.test.com/cat-registered-component;1 +category test-cat CatAppRegisteredComponent @unit.test.com/cat-app-registered-component;1 application={adb42a9a-0d19-4849-bf4d-627614ca19be} diff --git a/js/xpconnect/tests/unit/bogus_element_type.jsm b/js/xpconnect/tests/unit/bogus_element_type.jsm new file mode 100644 index 000000000..ba6583bba --- /dev/null +++ b/js/xpconnect/tests/unit/bogus_element_type.jsm @@ -0,0 +1 @@ +this.EXPORTED_SYMBOLS = [{}]; diff --git a/js/xpconnect/tests/unit/bogus_exports_type.jsm b/js/xpconnect/tests/unit/bogus_exports_type.jsm new file mode 100644 index 000000000..4ec23b215 --- /dev/null +++ b/js/xpconnect/tests/unit/bogus_exports_type.jsm @@ -0,0 +1 @@ +this.EXPORTED_SYMBOLS = "not an array"; diff --git a/js/xpconnect/tests/unit/bug451678_subscript.js b/js/xpconnect/tests/unit/bug451678_subscript.js new file mode 100644 index 000000000..72ff49a2d --- /dev/null +++ b/js/xpconnect/tests/unit/bug451678_subscript.js @@ -0,0 +1,5 @@ +var tags = []; +function makeTags() {} + +// This will be the return value of the script. +42 diff --git a/js/xpconnect/tests/unit/component-blob.js b/js/xpconnect/tests/unit/component-blob.js new file mode 100644 index 000000000..7c50f7c92 --- /dev/null +++ b/js/xpconnect/tests/unit/component-blob.js @@ -0,0 +1,79 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.importGlobalProperties(['Blob', 'File']); + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +function do_check_true(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; +} + +function BlobComponent() { + this.wrappedJSObject = this; +} +BlobComponent.prototype = +{ + doTest: function() { + // throw if anything goes wrong + let testContent = "hey!<\/b><\/a>"; + // should be able to construct a file + var f1 = new Blob([testContent], {"type" : "text/xml"}); + + // do some tests + do_check_true(f1 instanceof Ci.nsIDOMBlob, "Should be a DOM Blob"); + + do_check_true(!(f1 instanceof File), "Should not be a DOM File"); + + do_check_true(f1.type == "text/xml", "Wrong type"); + + do_check_true(f1.size == testContent.length, "Wrong content size"); + + var f2 = new Blob(); + do_check_true(f2.size == 0, "Wrong size"); + do_check_true(f2.type == "", "Wrong type"); + + var threw = false; + try { + // Needs a valid ctor argument + var f2 = new Blob(Date(132131532)); + } catch (e) { + threw = true; + } + do_check_true(threw, "Passing a random object should fail"); + + return true; + }, + + // nsIClassInfo + information for XPCOM registration code in XPCOMUtils.jsm + classDescription: "Blob in components scope code", + classID: Components.ID("{06215993-a3c2-41e3-bdfd-0a3a2cc0b65c}"), + contractID: "@mozilla.org/tests/component-blob;1", + + // nsIClassInfo + flags: 0, + + getInterfaces: function getInterfaces(aCount) { + var interfaces = [Components.interfaces.nsIClassInfo]; + aCount.value = interfaces.length; + return interfaces; + }, + + getScriptableHelper: function getScriptableHelper() { + return null; + }, + + // nsISupports + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIClassInfo]) +}; + +var gComponentsArray = [BlobComponent]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(gComponentsArray); diff --git a/js/xpconnect/tests/unit/component-blob.manifest b/js/xpconnect/tests/unit/component-blob.manifest new file mode 100644 index 000000000..ac264c06d --- /dev/null +++ b/js/xpconnect/tests/unit/component-blob.manifest @@ -0,0 +1,2 @@ +component {06215993-a3c2-41e3-bdfd-0a3a2cc0b65c} component-blob.js +contract @mozilla.org/tests/component-blob;1 {06215993-a3c2-41e3-bdfd-0a3a2cc0b65c} diff --git a/js/xpconnect/tests/unit/component-file.js b/js/xpconnect/tests/unit/component-file.js new file mode 100644 index 000000000..a05ae94e2 --- /dev/null +++ b/js/xpconnect/tests/unit/component-file.js @@ -0,0 +1,104 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); +Components.utils.importGlobalProperties(['File']); + + +const Ci = Components.interfaces; + +function do_check_true(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; +} + +function FileComponent() { + this.wrappedJSObject = this; +} +FileComponent.prototype = +{ + doTest: function() { + // throw if anything goes wrong + + // find the current directory path + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + file.append("xpcshell.ini"); + + // should be able to construct a file + var f1 = File.createFromFileName(file.path); + // and with nsIFiles + var f2 = File.createFromNsIFile(file); + + // do some tests + do_check_true(f1 instanceof File, "Should be a DOM File"); + do_check_true(f2 instanceof File, "Should be a DOM File"); + + do_check_true(f1.name == "xpcshell.ini", "Should be the right file"); + do_check_true(f2.name == "xpcshell.ini", "Should be the right file"); + + do_check_true(f1.type == "", "Should be the right type"); + do_check_true(f2.type == "", "Should be the right type"); + + var threw = false; + try { + // Needs a ctor argument + var f7 = new File(); + } catch (e) { + threw = true; + } + do_check_true(threw, "No ctor arguments should throw"); + + var threw = false; + try { + // Needs a valid ctor argument + var f7 = new File(Date(132131532)); + } catch (e) { + threw = true; + } + do_check_true(threw, "Passing a random object should fail"); + + var threw = false + try { + // Directories fail + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + var f7 = File.createFromNsIFile(dir) + } catch (e) { + threw = true; + } + do_check_true(threw, "Can't create a File object for a directory"); + + return true; + }, + + // nsIClassInfo + information for XPCOM registration code in XPCOMUtils.jsm + classDescription: "File in components scope code", + classID: Components.ID("{da332370-91d4-464f-a730-018e14769cab}"), + contractID: "@mozilla.org/tests/component-file;1", + + // nsIClassInfo + flags: 0, + + getInterfaces: function getInterfaces(aCount) { + var interfaces = [Components.interfaces.nsIClassInfo]; + aCount.value = interfaces.length; + return interfaces; + }, + + getScriptableHelper: function getScriptableHelper() { + return null; + }, + + // nsISupports + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIClassInfo]) +}; + +var gComponentsArray = [FileComponent]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(gComponentsArray); diff --git a/js/xpconnect/tests/unit/component-file.manifest b/js/xpconnect/tests/unit/component-file.manifest new file mode 100644 index 000000000..f865f333d --- /dev/null +++ b/js/xpconnect/tests/unit/component-file.manifest @@ -0,0 +1,2 @@ +component {da332370-91d4-464f-a730-018e14769cab} component-file.js +contract @mozilla.org/tests/component-file;1 {da332370-91d4-464f-a730-018e14769cab} \ No newline at end of file diff --git a/js/xpconnect/tests/unit/component_import.js b/js/xpconnect/tests/unit/component_import.js new file mode 100644 index 000000000..8d7e3d0cf --- /dev/null +++ b/js/xpconnect/tests/unit/component_import.js @@ -0,0 +1,86 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function FooComponent() { + this.wrappedJSObject = this; +} +FooComponent.prototype = +{ + // nsIClassInfo + information for XPCOM registration code in XPCOMUtils.jsm + classDescription: "Foo Component", + classID: Components.ID("{6b933fe6-6eba-4622-ac86-e4f654f1b474}"), + contractID: "@mozilla.org/tests/module-importer;1", + + // nsIClassInfo + flags: 0, + + getInterfaces: function getInterfaces(aCount) { + var interfaces = [Components.interfaces.nsIClassInfo]; + aCount.value = interfaces.length; + + // Guerilla test for line numbers hiding in this method + var threw = true; + try { + thereIsNoSuchIdentifier; + threw = false; + } catch (ex) { + do_check_true(ex.lineNumber == 27); + } + do_check_true(threw); + + return interfaces; + }, + + getScriptableHelper: function getScriptableHelper() { + return null; + }, + + // nsISupports + QueryInterface: function QueryInterface(aIID) { + if (aIID.equals(Components.interfaces.nsIClassInfo) || + aIID.equals(Components.interfaces.nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } +}; + +function BarComponent() { +} +BarComponent.prototype = +{ + // nsIClassInfo + information for XPCOM registration code in XPCOMUtils.jsm + classDescription: "Module importer test 2", + classID: Components.ID("{708a896a-b48d-4bff-906e-fc2fd134b296}"), + contractID: "@mozilla.org/tests/module-importer;2", + + // nsIClassInfo + flags: 0, + + getInterfaces: function getInterfaces(aCount) { + var interfaces = [Components.interfaces.nsIClassInfo]; + aCount.value = interfaces.length; + return interfaces; + }, + + getScriptableHelper: function getScriptableHelper() { + return null; + }, + + // nsISupports + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIClassInfo]) +}; + +function do_check_true(cond, text) { + // we don't have the test harness' utilities in this scope, so we need this + // little helper. In the failure case, the exception is propagated to the + // caller in the main run_test() function, and the test fails. + if (!cond) + throw "Failed check: " + text; +} + +var gComponentsArray = [FooComponent, BarComponent]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(gComponentsArray); diff --git a/js/xpconnect/tests/unit/component_import.manifest b/js/xpconnect/tests/unit/component_import.manifest new file mode 100644 index 000000000..952c2dd4d --- /dev/null +++ b/js/xpconnect/tests/unit/component_import.manifest @@ -0,0 +1,5 @@ +component {6b933fe6-6eba-4622-ac86-e4f654f1b474} component_import.js +contract @mozilla.org/tests/module-importer;1 {6b933fe6-6eba-4622-ac86-e4f654f1b474} + +component {708a896a-b48d-4bff-906e-fc2fd134b296} component_import.js +contract @mozilla.org/tests/module-importer;2 {708a896a-b48d-4bff-906e-fc2fd134b296} diff --git a/js/xpconnect/tests/unit/head_ongc.js b/js/xpconnect/tests/unit/head_ongc.js new file mode 100644 index 000000000..85989f1d9 --- /dev/null +++ b/js/xpconnect/tests/unit/head_ongc.js @@ -0,0 +1,41 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/jsdebugger.jsm"); + +const testingFunctions = Cu.getJSTestingFunctions(); +const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +function addTestingFunctionsToGlobal(global) { + for (let k in testingFunctions) { + global[k] = testingFunctions[k]; + } + global.print = do_print; + global.newGlobal = newGlobal; + addDebuggerToGlobal(global); +} + +function newGlobal() { + const global = new Cu.Sandbox(systemPrincipal, { freshZone: true }); + addTestingFunctionsToGlobal(global); + return global; +} + +addTestingFunctionsToGlobal(this); + +function executeSoon(f) { + Services.tm.mainThread.dispatch({ run: f }, + Ci.nsIThread.DISPATCH_NORMAL); +} + +// The onGarbageCollection tests don't play well gczeal settings and lead to +// intermittents. +if (typeof gczeal == "function") { + gczeal(0); +} + +// Make sure to GC before we start the test, so that no zones are scheduled for +// GC before we start testing onGarbageCollection hooks. +gc(); diff --git a/js/xpconnect/tests/unit/head_watchdog.js b/js/xpconnect/tests/unit/head_watchdog.js new file mode 100644 index 000000000..cf006303a --- /dev/null +++ b/js/xpconnect/tests/unit/head_watchdog.js @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// +// Pref management. +// + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://testing-common/PromiseTestUtils.jsm"); + +/////////////////// +// +// Whitelisting these tests. +// As part of bug 1077403, the shutdown crash should be fixed. +// +// These tests may crash intermittently on shutdown if the DOM Promise uncaught +// rejection observers are still registered when the watchdog operates. +PromiseTestUtils.thisTestLeaksUncaughtRejectionsAndShouldBeFixed(); + +var gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); + +function setWatchdogEnabled(enabled) { + gPrefs.setBoolPref("dom.use_watchdog", enabled); +} + +function isWatchdogEnabled() { + return gPrefs.getBoolPref("dom.use_watchdog"); +} + +function setScriptTimeout(seconds) { + var oldTimeout = gPrefs.getIntPref("dom.max_script_run_time"); + gPrefs.setIntPref("dom.max_script_run_time", seconds); + return oldTimeout; +} + +// +// Utilities. +// + +function busyWait(ms) { + var start = new Date(); + while ((new Date()) - start < ms) {} +} + +function do_log_info(aMessage) +{ + print("TEST-INFO | " + _TEST_FILE + " | " + aMessage); +} + +// We don't use do_execute_soon, because that inserts a +// do_test_{pending,finished} pair that gets screwed up when we terminate scripts +// from the operation callback. +function executeSoon(fn) { + var tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager); + tm.mainThread.dispatch({run: fn}, Ci.nsIThread.DISPATCH_NORMAL); +} + +// +// Asynchronous watchdog diagnostics. +// +// When running, the watchdog wakes up every second, and fires the operation +// callback if the script has been running for >= the minimum script timeout. +// As such, if the script timeout is 1 second, a script should never be able to +// run for two seconds or longer without servicing the operation callback. +// We wait 3 seconds, just to be safe. +// + +function checkWatchdog(expectInterrupt, continuation) { + var oldTimeout = setScriptTimeout(1); + var lastWatchdogWakeup = Cu.getWatchdogTimestamp("WatchdogWakeup"); + setInterruptCallback(function() { + // If the watchdog didn't actually trigger the operation callback, ignore + // this call. This allows us to test the actual watchdog behavior without + // interference from other sites where we trigger the operation callback. + if (lastWatchdogWakeup == Cu.getWatchdogTimestamp("WatchdogWakeup")) { + return true; + } + do_check_true(expectInterrupt); + setInterruptCallback(undefined); + setScriptTimeout(oldTimeout); + // Schedule our continuation before we kill this script. + executeSoon(continuation); + return false; + }); + executeSoon(function() { + busyWait(3000); + do_check_true(!expectInterrupt); + setInterruptCallback(undefined); + setScriptTimeout(oldTimeout); + continuation(); + }); +} + +var gGenerator; +function continueTest() { + gGenerator.next(); +} + +function run_test() { + + // Run async. + do_test_pending(); + + // Instantiate the generator and kick it off. + gGenerator = testBody(); + gGenerator.next(); +} + diff --git a/js/xpconnect/tests/unit/importer.jsm b/js/xpconnect/tests/unit/importer.jsm new file mode 100644 index 000000000..f5f40d30b --- /dev/null +++ b/js/xpconnect/tests/unit/importer.jsm @@ -0,0 +1 @@ +Components.utils.import("resource://test/syntax_error.jsm"); \ No newline at end of file diff --git a/js/xpconnect/tests/unit/recursive_importA.jsm b/js/xpconnect/tests/unit/recursive_importA.jsm new file mode 100644 index 000000000..ebd9a2ddd --- /dev/null +++ b/js/xpconnect/tests/unit/recursive_importA.jsm @@ -0,0 +1,12 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ["foo", "bar"]; + +function foo() { + return "foo"; +} + +var bar = {} +Components.utils.import("resource://test/recursive_importB.jsm", bar); diff --git a/js/xpconnect/tests/unit/recursive_importB.jsm b/js/xpconnect/tests/unit/recursive_importB.jsm new file mode 100644 index 000000000..6ef2b8644 --- /dev/null +++ b/js/xpconnect/tests/unit/recursive_importB.jsm @@ -0,0 +1,13 @@ +/* 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/. */ + +this.EXPORTED_SYMBOLS = ["baz", "qux"]; + +function baz() { + return "baz"; +} + +var qux = {} +Components.utils.import("resource://test/recursive_importA.jsm", qux); + diff --git a/js/xpconnect/tests/unit/subScriptWithEarlyError.js b/js/xpconnect/tests/unit/subScriptWithEarlyError.js new file mode 100644 index 000000000..45b904d1f --- /dev/null +++ b/js/xpconnect/tests/unit/subScriptWithEarlyError.js @@ -0,0 +1 @@ +var ; diff --git a/js/xpconnect/tests/unit/syntax_error.jsm b/js/xpconnect/tests/unit/syntax_error.jsm new file mode 100644 index 000000000..fca785bcd --- /dev/null +++ b/js/xpconnect/tests/unit/syntax_error.jsm @@ -0,0 +1 @@ +bogusjs)( diff --git a/js/xpconnect/tests/unit/test_URLSearchParams.js b/js/xpconnect/tests/unit/test_URLSearchParams.js new file mode 100644 index 000000000..0031a5ab6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_URLSearchParams.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var Cu = Components.utils; + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["URLSearchParams"] }); + sb.do_check_eq = do_check_eq; + Cu.evalInSandbox('do_check_eq(new URLSearchParams("one=1&two=2").get("one"), "1");', + sb); + Cu.importGlobalProperties(["URLSearchParams"]); + do_check_eq(new URLSearchParams("one=1&two=2").get("one"), "1"); +} diff --git a/js/xpconnect/tests/unit/test_allowWaivers.js b/js/xpconnect/tests/unit/test_allowWaivers.js new file mode 100644 index 000000000..66f70694a --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowWaivers.js @@ -0,0 +1,30 @@ +const Cu = Components.utils; +function checkWaivers(from, allowed) { + var sb = new Cu.Sandbox('http://example.com'); + from.test = sb.eval('var o = {prop: 2, f: function() {return 42;}}; o'); + + // Make sure that |from| has Xrays to sb. + do_check_eq(from.eval('test.prop'), 2); + do_check_eq(from.eval('test.f'), undefined); + + // Make sure that waivability works as expected. + do_check_eq(from.eval('!!test.wrappedJSObject'), allowed); + do_check_eq(from.eval('XPCNativeWrapper.unwrap(test) !== test'), allowed); + + // Make a sandbox with the same principal as |from|, but without any waiver + // restrictions, and make sure that the waiver does not transfer. + var friend = new Cu.Sandbox(Cu.getObjectPrincipal(from)); + friend.test = from.test; + friend.eval('var waived = test.wrappedJSObject;'); + do_check_true(friend.eval('waived.f()'), 42); + friend.from = from; + friend.eval('from.waived = waived'); + do_check_eq(from.eval('!!waived.f'), allowed); +} + +function run_test() { + checkWaivers(new Cu.Sandbox('http://example.com'), true); + checkWaivers(new Cu.Sandbox('http://example.com', {allowWaivers: false}), false); + checkWaivers(new Cu.Sandbox(['http://example.com']), true); + checkWaivers(new Cu.Sandbox(['http://example.com'], {allowWaivers: false}), false); +} diff --git a/js/xpconnect/tests/unit/test_allowedDomains.js b/js/xpconnect/tests/unit/test_allowedDomains.js new file mode 100644 index 000000000..a85d9d75a --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowedDomains.js @@ -0,0 +1,47 @@ +function run_test() { + var cu = Components.utils; + var sbMaster = cu.Sandbox(["http://www.a.com", + "http://www.b.com", + "http://www.d.com"]); + var sbSubset = cu.Sandbox(["http://www.d.com", + "http://www.a.com"]); + + var sbA = cu.Sandbox("http://www.a.com"); + var sbB = cu.Sandbox("http://www.b.com"); + var sbC = cu.Sandbox("http://www.c.com"); + + sbMaster.objA = cu.evalInSandbox("var obj = {prop1:200}; obj", sbA); + sbMaster.objB = cu.evalInSandbox("var obj = {prop1:200}; obj", sbB); + sbMaster.objC = cu.evalInSandbox("var obj = {prop1:200}; obj", sbC); + sbMaster.objOwn = cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + + sbMaster.objSubset = cu.evalInSandbox("var obj = {prop1:200}; obj", sbSubset); + sbA.objMaster = cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + sbSubset.objMaster = cu.evalInSandbox("var obj = {prop1:200}; obj", sbMaster); + + var ret; + ret = cu.evalInSandbox("objA.prop1", sbMaster); + do_check_eq(ret, 200); + ret = cu.evalInSandbox("objB.prop1", sbMaster); + do_check_eq(ret, 200); + ret = cu.evalInSandbox("objSubset.prop1", sbMaster); + do_check_eq(ret, 200); + + function evalAndCatch(str, sb) { + try { + ret = cu.evalInSandbox(str, sb); + do_check_true(false, "unexpected pass") + } catch (e) { + do_check_true(e.message && e.message.indexOf("Permission denied to access property") != -1); + } + } + + evalAndCatch("objC.prop1", sbMaster); + evalAndCatch("objMaster.prop1", sbA); + evalAndCatch("objMaster.prop1", sbSubset); + + // Bug 777705: + sbMaster.Components = cu.getComponentsForScope(sbMaster); + Components.utils.evalInSandbox("Components.interfaces", sbMaster); + do_check_true(true); +} diff --git a/js/xpconnect/tests/unit/test_allowedDomainsXHR.js b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js new file mode 100644 index 000000000..71bfe942b --- /dev/null +++ b/js/xpconnect/tests/unit/test_allowedDomainsXHR.js @@ -0,0 +1,136 @@ + +var cu = Components.utils; +cu.import("resource://testing-common/httpd.js"); + +var httpserver = new HttpServer(); +var httpserver2 = new HttpServer(); +var httpserver3 = new HttpServer(); +var testpath = "/simple"; +var redirectpath = "/redirect"; +var negativetestpath = "/negative"; +var httpbody = "0123456789"; + +var sb = cu.Sandbox(["http://www.example.com", + "http://localhost:4444/redirect", + "http://localhost:4444/simple", + "http://localhost:4446/redirect"], + { wantGlobalProperties: ["XMLHttpRequest"] }); + +function createXHR(loc, async) +{ + var xhr = new XMLHttpRequest(); + xhr.open("GET", "http://localhost:" + loc, async); + return xhr; +} + +function checkResults(xhr) +{ + if (xhr.readyState != 4) + return false; + + do_check_eq(xhr.status, 200); + do_check_eq(xhr.responseText, httpbody); + + var root_node = xhr.responseXML.getElementsByTagName('root').item(0); + do_check_eq(root_node.firstChild.data, "0123456789"); + return true; +} + +var httpServersClosed = 0; +function finishIfDone() +{ + if (++httpServersClosed == 3) + do_test_finished(); +} + +function run_test() +{ + do_test_pending(); + + httpserver.registerPathHandler(testpath, serverHandler); + httpserver.registerPathHandler(redirectpath, redirectHandler1); + httpserver.start(4444); + + httpserver2.registerPathHandler(negativetestpath, serverHandler); + httpserver2.start(4445); + + httpserver3.registerPathHandler(redirectpath, redirectHandler2); + httpserver3.start(4446); + + // Test sync XHR sending + cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = cu.evalInSandbox('var sync = createXHR("4444/simple"); sync.send(null); sync', sb); + do_check_true(checkResults(res)); + + var principal = res.responseXML.nodePrincipal; + do_check_true(principal.isCodebasePrincipal); + var requestURL = "http://localhost:4444/simple"; + do_check_eq(principal.URI.spec, requestURL); + + // negative test sync XHR sending (to ensure that the xhr do not have chrome caps, see bug 779821) + try { + cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = cu.evalInSandbox('var sync = createXHR("4445/negative"); sync.send(null); sync', sb); + do_check_false(true, "XHR created from sandbox should not have chrome caps"); + } catch (e) { + do_check_true(true); + } + + // Test redirect handling. + // This request bounces to server 2 and then back to server 1. Neither of + // these servers support CORS, but if the expanded principal is used as the + // triggering principal, this should work. + cu.evalInSandbox('var createXHR = ' + createXHR.toString(), sb); + var res = cu.evalInSandbox('var sync = createXHR("4444/redirect"); sync.send(null); sync', sb); + do_check_true(checkResults(res)); + + var principal = res.responseXML.nodePrincipal; + do_check_true(principal.isCodebasePrincipal); + var requestURL = "http://localhost:4444/simple"; + do_check_eq(principal.URI.spec, requestURL); + + httpserver2.stop(finishIfDone); + httpserver3.stop(finishIfDone); + + // Test async XHR sending + sb.finish = function(){ + httpserver.stop(finishIfDone); + } + + // We want to execute checkResults from the scope of the sandbox as well to + // make sure that there are no permission errors related to nsEP. For that + // we need to clone the function into the sandbox and make a few things + // available for it. + cu.evalInSandbox('var checkResults = ' + checkResults.toSource(), sb); + sb.do_check_eq = do_check_eq; + sb.httpbody = httpbody; + + function changeListener(event) { + if (checkResults(async)) + finish(); + } + + var async = cu.evalInSandbox('var async = createXHR("4444/simple", true);' + + 'async.addEventListener("readystatechange", ' + + changeListener.toString() + ', false);' + + 'async', sb); + async.send(null); +} + +function serverHandler(request, response) +{ + response.setHeader("Content-Type", "text/xml", false); + response.bodyOutputStream.write(httpbody, httpbody.length); +} + +function redirectHandler1(request, response) +{ + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "http://localhost:4446/redirect", false); +} + +function redirectHandler2(request, response) +{ + response.setStatusLine(request.httpVersion, 302, "Found"); + response.setHeader("Location", "http://localhost:4444/simple", false); +} diff --git a/js/xpconnect/tests/unit/test_asyncLoadSubScriptError.js b/js/xpconnect/tests/unit/test_asyncLoadSubScriptError.js new file mode 100644 index 000000000..0c27392d8 --- /dev/null +++ b/js/xpconnect/tests/unit/test_asyncLoadSubScriptError.js @@ -0,0 +1,33 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +var srvScope = {}; + +function success(result) { + ok(false, "script should not have loaded successfully"); + do_test_finished(); +} + +function error(err) { + ok(err instanceof SyntaxError, "loading script with early error asynchronously resulted in error"); + do_test_finished(); +} + +function run_test() { + do_test_pending(); + + var file = do_get_file("subScriptWithEarlyError.js"); + var ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + var uri = ios.newFileURI(file); + var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + var p = scriptLoader.loadSubScriptWithOptions(uri.spec, + { target: srvScope, + async: true }); + p.then(success, error); +} diff --git a/js/xpconnect/tests/unit/test_attributes.js b/js/xpconnect/tests/unit/test_attributes.js new file mode 100644 index 000000000..52dbb59e6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_attributes.js @@ -0,0 +1,81 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +function run_test() { + + // Load the component manifests. + registerAppManifest(do_get_file('../components/native/chrome.manifest')); + registerAppManifest(do_get_file('../components/js/xpctest.manifest')); + + // Test for each component. + test_component_readwrite("@mozilla.org/js/xpc/test/native/ObjectReadWrite;1"); + test_component_readwrite("@mozilla.org/js/xpc/test/js/ObjectReadWrite;1"); + test_component_readonly("@mozilla.org/js/xpc/test/native/ObjectReadOnly;1"); + test_component_readonly("@mozilla.org/js/xpc/test/js/ObjectReadOnly;1"); +} + +function test_component_readwrite(contractid) { + + // Instantiate the object. + var o = Cc[contractid].createInstance(Ci["nsIXPCTestObjectReadWrite"]); + + // Test the initial values. + do_check_eq("XPConnect Read-Writable String", o.stringProperty); + do_check_eq(true, o.booleanProperty); + do_check_eq(32767, o.shortProperty); + do_check_eq(2147483647, o.longProperty); + do_check_true(5.25 < o.floatProperty && 5.75 > o.floatProperty); + do_check_eq("X", o.charProperty); + do_check_eq(-1, o.timeProperty); + + // Write new values. + o.stringProperty = "another string"; + o.booleanProperty = false; + o.shortProperty = -12345; + o.longProperty = 1234567890; + o.floatProperty = 10.2; + o.charProperty = "Z"; + o.timeProperty = 1; + + // Test the new values. + do_check_eq("another string", o.stringProperty); + do_check_eq(false, o.booleanProperty); + do_check_eq(-12345, o.shortProperty); + do_check_eq(1234567890, o.longProperty); + do_check_true(10.15 < o.floatProperty && 10.25 > o.floatProperty); + do_check_eq("Z", o.charProperty); + do_check_eq(1, o.timeProperty); + + // Assign values that differ from the expected type to verify conversion. + + function SetAndTestBooleanProperty(newValue, expectedValue) { + o.booleanProperty = newValue; + do_check_eq(expectedValue, o.booleanProperty); + }; + SetAndTestBooleanProperty(false, false); + SetAndTestBooleanProperty(1, true); + SetAndTestBooleanProperty(null, false); + SetAndTestBooleanProperty("A", true); + SetAndTestBooleanProperty(undefined, false); + SetAndTestBooleanProperty([], true); + SetAndTestBooleanProperty({}, true); +} + +function test_component_readonly(contractid) { + + // Instantiate the object. + var o = Cc[contractid].createInstance(Ci["nsIXPCTestObjectReadOnly"]); + + // Test the initial values. + do_check_eq("XPConnect Read-Only String", o.strReadOnly); + do_check_eq(true, o.boolReadOnly); + do_check_eq(32767, o.shortReadOnly); + do_check_eq(2147483647, o.longReadOnly); + do_check_true(5.25 < o.floatReadOnly && 5.75 > o.floatReadOnly); + do_check_eq("X", o.charReadOnly); + do_check_eq(-1, o.timeReadOnly); +} diff --git a/js/xpconnect/tests/unit/test_blob.js b/js/xpconnect/tests/unit/test_blob.js new file mode 100644 index 000000000..039f45102 --- /dev/null +++ b/js/xpconnect/tests/unit/test_blob.js @@ -0,0 +1,16 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + do_load_manifest("component-blob.manifest"); + const contractID = "@mozilla.org/tests/component-blob;1"; + do_check_true(contractID in Components.classes); + var foo = Components.classes[contractID] + .createInstance(Components.interfaces.nsIClassInfo); + do_check_true(Boolean(foo)); + do_check_true(foo.contractID == contractID); + do_check_true(!!foo.wrappedJSObject); + do_check_true(foo.wrappedJSObject.doTest()); + +} diff --git a/js/xpconnect/tests/unit/test_blob2.js b/js/xpconnect/tests/unit/test_blob2.js new file mode 100644 index 000000000..e031d0813 --- /dev/null +++ b/js/xpconnect/tests/unit/test_blob2.js @@ -0,0 +1,36 @@ +/* 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/. */ + +Components.utils.importGlobalProperties(['Blob', 'File']); + +const Ci = Components.interfaces; + +function run_test() { + // throw if anything goes wrong + let testContent = "hey!<\/b><\/a>"; + // should be able to construct a file + var f1 = new Blob([testContent], {"type" : "text/xml"}); + + // do some tests + do_check_true(f1 instanceof Ci.nsIDOMBlob, "Should be a DOM Blob"); + + do_check_true(!(f1 instanceof File), "Should not be a DOM File"); + + do_check_true(f1.type == "text/xml", "Wrong type"); + + do_check_true(f1.size == testContent.length, "Wrong content size"); + + var f2 = new Blob(); + do_check_true(f2.size == 0, "Wrong size"); + do_check_true(f2.type == "", "Wrong type"); + + var threw = false; + try { + // Needs a valid ctor argument + var f2 = new Blob(Date(132131532)); + } catch (e) { + threw = true; + } + do_check_true(threw, "Passing a random object should fail"); +} diff --git a/js/xpconnect/tests/unit/test_bogus_files.js b/js/xpconnect/tests/unit/test_bogus_files.js new file mode 100644 index 000000000..dc24a5fbd --- /dev/null +++ b/js/xpconnect/tests/unit/test_bogus_files.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/. */ + +function test_BrokenFile(path, shouldThrow, expectedName) { + var didThrow = false; + try { + Components.utils.import(path); + } catch (ex) { + var exceptionName = ex.name; + print("ex: " + ex + "; name = " + ex.name); + didThrow = true; + } + + do_check_eq(didThrow, shouldThrow); + if (didThrow) + do_check_eq(exceptionName, expectedName); +} + +function run_test() { + test_BrokenFile("resource://test/bogus_exports_type.jsm", true, "Error"); + + test_BrokenFile("resource://test/bogus_element_type.jsm", true, "Error"); + + test_BrokenFile("resource://test/non_existing.jsm", + true, + "NS_ERROR_FILE_NOT_FOUND"); + + test_BrokenFile("chrome://test/content/test.jsm", + true, + "NS_ERROR_FILE_NOT_FOUND"); + + // check that we can access modules' global objects even if + // EXPORTED_SYMBOLS is missing or ill-formed: + do_check_eq(typeof(Components.utils.import("resource://test/bogus_exports_type.jsm", + null)), + "object"); + + do_check_eq(typeof(Components.utils.import("resource://test/bogus_element_type.jsm", + null)), + "object"); +} diff --git a/js/xpconnect/tests/unit/test_bug1001094.js b/js/xpconnect/tests/unit/test_bug1001094.js new file mode 100644 index 000000000..f324e6b3f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1001094.js @@ -0,0 +1,4 @@ +function run_test() { + // Make sure nsJSID implements classinfo. + do_check_eq(Components.ID("{a6e2a27f-5521-4b35-8b52-99799a744aee}").equals, Components.ID("{daa47351-7d2e-44a7-b8e3-281802a1eab7}").equals); +} diff --git a/js/xpconnect/tests/unit/test_bug1021312.js b/js/xpconnect/tests/unit/test_bug1021312.js new file mode 100644 index 000000000..90009ed14 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1021312.js @@ -0,0 +1,16 @@ +/* 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 Cu = Components.utils; +function run_test() { + let sb = new Cu.Sandbox(this); + var called = false; + + Cu.exportFunction(function(str) { do_check_true(/someString/.test(str)); called = true; }, + sb, { defineAs: "func" }); + // Do something weird with the string to make sure that it doesn't get interned. + Cu.evalInSandbox("var str = 'someString'; for (var i = 0; i < 10; ++i) str += i;", sb); + Cu.evalInSandbox("func(str);", sb); + do_check_true(called); +} diff --git a/js/xpconnect/tests/unit/test_bug1033253.js b/js/xpconnect/tests/unit/test_bug1033253.js new file mode 100644 index 000000000..9bae251ba --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033253.js @@ -0,0 +1,6 @@ +const Cu = Components.utils; +function run_test() { + var sb = Cu.Sandbox('http://www.example.com'); + var f = Cu.evalInSandbox('var f = function() {}; f;', sb); + do_check_eq(f.name, ""); +} diff --git a/js/xpconnect/tests/unit/test_bug1033920.js b/js/xpconnect/tests/unit/test_bug1033920.js new file mode 100644 index 000000000..dc3834ecb --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033920.js @@ -0,0 +1,7 @@ +const Cu = Components.utils; +function run_test() { + var sb = Cu.Sandbox('http://www.example.com'); + var o = new sb.Object(); + o.__proto__ = null; + do_check_eq(Object.getPrototypeOf(o), null); +} diff --git a/js/xpconnect/tests/unit/test_bug1033927.js b/js/xpconnect/tests/unit/test_bug1033927.js new file mode 100644 index 000000000..8c3ce7c26 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1033927.js @@ -0,0 +1,8 @@ +const Cu = Components.utils; +function run_test() { + var sb = Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ['XMLHttpRequest']}); + var xhr = Cu.evalInSandbox('new XMLHttpRequest()', sb); + do_check_eq(xhr.toString(), '[object XMLHttpRequest]'); + do_check_eq((new sb.Object()).toString(), '[object Object]'); + do_check_eq(sb.Object.prototype.toString.call(new sb.Uint16Array()), '[object Uint16Array]'); +} diff --git a/js/xpconnect/tests/unit/test_bug1034262.js b/js/xpconnect/tests/unit/test_bug1034262.js new file mode 100644 index 000000000..a77df56a9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1034262.js @@ -0,0 +1,9 @@ +const Cu = Components.utils; +function run_test() { + var sb1 = Cu.Sandbox('http://www.example.com', { wantXrays: true }); + var sb2 = Cu.Sandbox('http://www.example.com', { wantXrays: false }); + sb2.f = Cu.evalInSandbox('x => typeof x', sb1); + do_check_eq(Cu.evalInSandbox('f(dump)', sb2), 'function'); + do_check_eq(Cu.evalInSandbox('f.call(null, dump)', sb2), 'function'); + do_check_eq(Cu.evalInSandbox('f.apply(null, [dump])', sb2), 'function'); +} diff --git a/js/xpconnect/tests/unit/test_bug1081990.js b/js/xpconnect/tests/unit/test_bug1081990.js new file mode 100644 index 000000000..30f7a80d9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1081990.js @@ -0,0 +1,10 @@ +const Cu = Components.utils; +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + sb.obj = {}; + sb.arr = []; + sb.fun = function() {}; + do_check_true(sb.eval('Object.getPrototypeOf(obj) == null')); + do_check_true(sb.eval('Object.getPrototypeOf(arr) == null')); + do_check_true(sb.eval('Object.getPrototypeOf(fun) == null')); +} diff --git a/js/xpconnect/tests/unit/test_bug1082450.js b/js/xpconnect/tests/unit/test_bug1082450.js new file mode 100644 index 000000000..07f45f06b --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1082450.js @@ -0,0 +1,40 @@ +const Cu = Components.utils; +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.com'); + function checkThrows(str, rgxp) { + try { + sb.eval(str); + do_check_true(false); + } catch (e) { + do_check_true(rgxp.test(e)); + } + } + + sb.exposed = { + get getterProp() { return 42; }, + set setterProp(x) { }, + get getterSetterProp() { return 42; }, + set getterSetterProp(x) { }, + simpleValueProp: 42, + objectValueProp: { val: 42, __exposedProps__: { val: 'r' } }, + contentCallableValueProp: new sb.Function('return 42'), + chromeCallableValueProp: function() {}, + __exposedProps__: { getterProp : 'r', + setterProp : 'w', + getterSetterProp: 'rw', + simpleValueProp: 'r', + objectValueProp: 'r', + contentCallableValueProp: 'r', + chromeCallableValueProp: 'r' } + }; + + do_check_eq(sb.eval('exposed.simpleValueProp'), 42); + do_check_eq(sb.eval('exposed.objectValueProp.val'), 42); + checkThrows('exposed.getterProp;', /privileged accessor/i); + checkThrows('exposed.setterProp = 42;', /privileged accessor/i); + checkThrows('exposed.getterSetterProp;', /privileged accessor/i); + checkThrows('exposed.getterSetterProp = 42;', /privileged accessor/i); + do_check_eq(sb.eval('exposed.contentCallableValueProp()'), 42); + checkThrows('exposed.chromeCallableValueProp();', /privileged or cross-origin callable/i); +} diff --git a/js/xpconnect/tests/unit/test_bug1110546.js b/js/xpconnect/tests/unit/test_bug1110546.js new file mode 100644 index 000000000..02687296d --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1110546.js @@ -0,0 +1,5 @@ +const Cu = Components.utils; +function run_test() { + var sb = new Cu.Sandbox(null); + do_check_true(Cu.getObjectPrincipal(sb).isNullPrincipal); +} diff --git a/js/xpconnect/tests/unit/test_bug1131707.js b/js/xpconnect/tests/unit/test_bug1131707.js new file mode 100644 index 000000000..4ae4404a0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1131707.js @@ -0,0 +1,22 @@ +const Cu = Components.utils; + +function testStrict(sb) { + "use strict"; + do_check_eq(sb.eval("typeof wrappedCtor()"), "string"); + do_check_eq(sb.eval("typeof new wrappedCtor()"), "object"); +} + +function run_test() { + var sb = new Cu.Sandbox(null); + var dateCtor = sb.Date; + sb.wrappedCtor = Cu.exportFunction(function wrapper(val) { + "use strict"; + var constructing = this.constructor == wrapper; + return constructing ? new dateCtor(val) : dateCtor(val); + }, sb); + do_check_eq(typeof Date(), "string"); + do_check_eq(typeof new Date(), "object"); + do_check_eq(sb.eval("typeof wrappedCtor()"), "string"); + do_check_eq(sb.eval("typeof new wrappedCtor()"), "object"); + testStrict(sb); +} diff --git a/js/xpconnect/tests/unit/test_bug1150106.js b/js/xpconnect/tests/unit/test_bug1150106.js new file mode 100644 index 000000000..0a93fb588 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1150106.js @@ -0,0 +1,35 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +var srvScope = {}; + +function success(result) { + equal(result, 42, "Result of script is correct"); + ok('makeTags' in srvScope && srvScope.makeTags instanceof Function, + "makeTags is a function."); + do_test_finished(); +} + +function error() { + ok(false, "error loading the script asynchronously."); + do_test_finished(); +} + +function run_test() { + do_test_pending(); + + var file = do_get_file("bug451678_subscript.js"); + var ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + var uri = ios.newFileURI(file); + var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + var p = scriptLoader.loadSubScriptWithOptions(uri.spec, + { target: srvScope, + async: true }); + p.then(success, error); +} diff --git a/js/xpconnect/tests/unit/test_bug1150771.js b/js/xpconnect/tests/unit/test_bug1150771.js new file mode 100644 index 000000000..8387b2cdf --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1150771.js @@ -0,0 +1,13 @@ +function run_test() { +let Cu = Components.utils; +let sandbox1 = new Cu.Sandbox(null); +let sandbox2 = new Cu.Sandbox(null); +let arg = Cu.evalInSandbox('({ buf: new ArrayBuffer(2) })', sandbox1); + +let clonedArg = Cu.cloneInto(Cu.waiveXrays(arg), sandbox2); +do_check_eq(typeof Cu.waiveXrays(clonedArg).buf, "object"); + +clonedArg = Cu.cloneInto(arg, sandbox2); +do_check_eq(typeof Cu.waiveXrays(clonedArg).buf, "object"); +do_check_eq(Cu.waiveXrays(clonedArg).buf.constructor.name, "ArrayBuffer"); +} diff --git a/js/xpconnect/tests/unit/test_bug1151385.js b/js/xpconnect/tests/unit/test_bug1151385.js new file mode 100644 index 000000000..fdf979fe0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1151385.js @@ -0,0 +1,9 @@ +function run_test() +{ + try { + var sandbox = new Components.utils.Sandbox(null, {"sandboxPrototype" : {}}); + do_check_true(false); + } catch (e) { + do_check_true(/must subsume sandboxPrototype/.test(e)); + } +} diff --git a/js/xpconnect/tests/unit/test_bug1170311.js b/js/xpconnect/tests/unit/test_bug1170311.js new file mode 100644 index 000000000..0c58c75d7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1170311.js @@ -0,0 +1,5 @@ +const Cu = Components.utils; +function run_test() { + do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).equals(null), "NS_ERROR_ILLEGAL_VALUE"); + do_check_throws_nsIException(() => Cu.getObjectPrincipal({}).subsumes(null), "NS_ERROR_ILLEGAL_VALUE"); +} diff --git a/js/xpconnect/tests/unit/test_bug1244222.js b/js/xpconnect/tests/unit/test_bug1244222.js new file mode 100644 index 000000000..a5cc39a41 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug1244222.js @@ -0,0 +1,27 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +function run_test() { + registerAppManifest(do_get_file('../components/js/xpctest.manifest')); + + // Generate a CCW to a function. + var sb = new Cu.Sandbox(this); + sb.eval('function fun(x) { return x; }'); + do_check_eq(sb.fun("foo"), "foo"); + + // Double-wrap the CCW. + var utils = Cc["@mozilla.org/js/xpc/test/js/TestUtils;1"].createInstance(Ci.nsIXPCTestUtils); + var doubleWrapped = utils.doubleWrapFunction(sb.fun); + do_check_eq(doubleWrapped.echo("foo"), "foo"); + + // GC. + Cu.forceGC(); + + // Make sure it still works. + do_check_eq(doubleWrapped.echo("foo"), "foo"); +} diff --git a/js/xpconnect/tests/unit/test_bug408412.js b/js/xpconnect/tests/unit/test_bug408412.js new file mode 100644 index 000000000..d3d60cd23 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug408412.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var file = do_get_file("syntax_error.jsm"); + var ios = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + var uri = ios.newFileURI(file); + + try { + Components.utils.import(uri.spec); + do_throw("Failed to report any error at all"); + } catch (e) { + do_check_neq(/^SyntaxError:/.exec(e + ''), null); + } +} diff --git a/js/xpconnect/tests/unit/test_bug451678.js b/js/xpconnect/tests/unit/test_bug451678.js new file mode 100644 index 000000000..a9454a26b --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug451678.js @@ -0,0 +1,18 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +function run_test() { + var file = do_get_file("bug451678_subscript.js"); + var ios = Cc["@mozilla.org/network/io-service;1"] + .getService(Ci.nsIIOService); + var uri = ios.newFileURI(file); + var scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"] + .getService(Ci.mozIJSSubScriptLoader); + var srvScope = {}; + scriptLoader.loadSubScript(uri.spec, srvScope); + do_check_true('makeTags' in srvScope && srvScope.makeTags instanceof Function); +} diff --git a/js/xpconnect/tests/unit/test_bug604362.js b/js/xpconnect/tests/unit/test_bug604362.js new file mode 100644 index 000000000..f158d9497 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug604362.js @@ -0,0 +1,12 @@ +function run_test() { + var Cc = Components.classes; + var Ci = Components.interfaces; + var sp = Cc["@mozilla.org/systemprincipal;1"]. + createInstance(Ci.nsIPrincipal); + var s = Components.utils.Sandbox(sp); + s.a = []; + s.Cu = Components.utils; + s.C = Components; + s.do_check_neq = do_check_neq; + Components.utils.evalInSandbox("do_check_neq(Cu.import, undefined);", s); +} diff --git a/js/xpconnect/tests/unit/test_bug677864.js b/js/xpconnect/tests/unit/test_bug677864.js new file mode 100644 index 000000000..a90a7b137 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug677864.js @@ -0,0 +1,11 @@ +function check_cl(iface, desc) { + do_check_eq(iface.QueryInterface(Components.interfaces.nsIClassInfo).classDescription, desc); +} + +function run_test() { + check_cl(Components.interfaces, 'XPCComponents_Interfaces'); + check_cl(Components.interfacesByID, 'XPCComponents_InterfacesByID'); + check_cl(Components.classes, 'XPCComponents_Classes'); + check_cl(Components.classesByID, 'XPCComponents_ClassesByID'); + check_cl(Components.results, 'XPCComponents_Results'); +} diff --git a/js/xpconnect/tests/unit/test_bug711404.js b/js/xpconnect/tests/unit/test_bug711404.js new file mode 100644 index 000000000..77a5ce40a --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug711404.js @@ -0,0 +1,10 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; + +function run_test() +{ + var p = Cc["@mozilla.org/hash-property-bag;1"]. + createInstance(Ci.nsIWritablePropertyBag2); + p.setPropertyAsInt64("a", -4000); + do_check_neq(p.getPropertyAsUint64("a"), -4000); +} diff --git a/js/xpconnect/tests/unit/test_bug742444.js b/js/xpconnect/tests/unit/test_bug742444.js new file mode 100644 index 000000000..8d8b5643a --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug742444.js @@ -0,0 +1,17 @@ +const Cu = Components.utils; +function run_test() { + let sb1A = Cu.Sandbox('http://www.example.com'); + let sb1B = Cu.Sandbox('http://www.example.com'); + let sb2 = Cu.Sandbox('http://www.example.org'); + let sbChrome = Cu.Sandbox(this); + let obj = new sb1A.Object(); + sb1B.obj = obj; + sb1B.waived = Cu.waiveXrays(obj); + sb2.obj = obj; + sb2.waived = Cu.waiveXrays(obj); + sbChrome.obj = obj; + sbChrome.waived = Cu.waiveXrays(obj); + do_check_true(Cu.evalInSandbox('obj === waived', sb1B)); + do_check_true(Cu.evalInSandbox('obj === waived', sb2)); + do_check_true(Cu.evalInSandbox('obj !== waived', sbChrome)); +} diff --git a/js/xpconnect/tests/unit/test_bug778409.js b/js/xpconnect/tests/unit/test_bug778409.js new file mode 100644 index 000000000..4a905ad0f --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug778409.js @@ -0,0 +1,11 @@ +function run_test() { + var Cu = Components.utils; + var sb1 = Cu.Sandbox('http://example.com'); + var sb2 = Cu.Sandbox('http://example.org'); + var chromeObj = {foo: 2}; + var sb1obj = Cu.evalInSandbox('new Object()', sb1); + chromeObj.__proto__ = sb1obj; + sb2.wrapMe = chromeObj; + do_check_true(true, "Didn't crash"); + do_check_eq(sb2.wrapMe.__proto__, sb1obj, 'proto set correctly'); +} diff --git a/js/xpconnect/tests/unit/test_bug780370.js b/js/xpconnect/tests/unit/test_bug780370.js new file mode 100644 index 000000000..40d6f9748 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug780370.js @@ -0,0 +1,23 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=780370 */ + +const Cu = Components.utils; + +// Use a COW to expose a function from a standard prototype, and make we deny +// access to it. + +function run_test() +{ + var sb = Cu.Sandbox("http://www.example.com"); + sb.obj = { foo: 42, __exposedProps__: { hasOwnProperty: 'r' } }; + do_check_eq(Cu.evalInSandbox('typeof obj.foo', sb), 'undefined', "COW works as expected"); + try { + Cu.evalInSandbox('obj.hasOwnProperty', sb); + do_check_true(false); + } catch (e) { + do_check_true(/privileged or cross-origin callable/i.test(e)); + } +} diff --git a/js/xpconnect/tests/unit/test_bug809652.js b/js/xpconnect/tests/unit/test_bug809652.js new file mode 100644 index 000000000..66dd6d550 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug809652.js @@ -0,0 +1,63 @@ +/* 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/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=813901 */ + +const Cu = Components.utils; +const TypedArrays = [ Int8Array, Uint8Array, Int16Array, Uint16Array, + Int32Array, Uint32Array, Float32Array, Float64Array, + Uint8ClampedArray ]; + +// Make sure that the correct nativecall-y stuff is denied on security wrappers. + +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.org'); + sb.obj = {foo: 2}; + + /* Set up some typed arrays. */ + sb.ab = new ArrayBuffer(8); + for (var i = 0; i < 8; ++i) + new Uint8Array(sb.ab)[i] = i * 10; + sb.ta = []; + TypedArrays.forEach(f => sb.ta.push(new f(sb.ab))); + sb.dv = new DataView(sb.ab); + + /* Things that should throw. */ + checkThrows("Object.prototype.__lookupSetter__('__proto__').call(obj, {});", sb); + sb.re = /f/; + checkThrows("RegExp.prototype.exec.call(re, 'abcdefg').index", sb); + sb.d = new Date(); + checkThrows("Date.prototype.setYear.call(d, 2011)", sb); + sb.m = new Map(); + checkThrows("(new Map()).clear.call(m)", sb); + checkThrows("ArrayBuffer.prototype.__lookupGetter__('byteLength').call(ab);", sb); + checkThrows("ArrayBuffer.prototype.slice.call(ab, 0);", sb); + checkThrows("DataView.prototype.getInt8.call(dv, 0);", sb); + + /* Now that Date is on Xrays, these should all throw. */ + checkThrows("Date.prototype.getYear.call(d)", sb); + checkThrows("Date.prototype.valueOf.call(d)", sb); + checkThrows("d.valueOf()", sb); + checkThrows("d.toString()", sb); + + /* Typed arrays. */ + function testForTypedArray(t) { + sb.curr = t; + sb.currName = t.constructor.name; + checkThrows("this[currName].prototype.subarray.call(curr, 0)[0]", sb); + checkThrows("(new this[currName]).__lookupGetter__('length').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('buffer').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('byteOffset').call(curr)", sb); + checkThrows("(new this[currName]).__lookupGetter__('byteLength').call(curr)", sb); + } + sb.ta.forEach(testForTypedArray); +} + +function checkThrows(expression, sb) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + dump('result: ' + result + '\n\n\n'); + do_check_true(!!/denied/.exec(result)); +} + diff --git a/js/xpconnect/tests/unit/test_bug809674.js b/js/xpconnect/tests/unit/test_bug809674.js new file mode 100644 index 000000000..76bfdf742 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug809674.js @@ -0,0 +1,31 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +function run_test() { + + // Load the component manifest. + Components.manager.autoRegister(do_get_file('../components/js/xpctest.manifest')); + + // Test for each component. + test_property_throws("@mozilla.org/js/xpc/test/js/Bug809674;1"); +} + +function test_property_throws(contractid) { + + // Instantiate the object. + var o = Cc[contractid].createInstance(Ci["nsIXPCTestBug809674"]); + + // Test the initial values. + try { + o.jsvalProperty; + do_check_true(false); + } catch (e) { + do_check_true(true); + do_check_true(/implicit_jscontext/.test(e)) + } + +} diff --git a/js/xpconnect/tests/unit/test_bug813901.js b/js/xpconnect/tests/unit/test_bug813901.js new file mode 100644 index 000000000..42f981581 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug813901.js @@ -0,0 +1,25 @@ +/* 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/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=813901 */ + +const Cu = Components.utils; + +// Make sure that we can't inject __exposedProps__ via the proto of a COW-ed object. + +function checkThrows(expression, sb, regexp) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + dump('result: ' + result + '\n\n\n'); + do_check_true(!!regexp.exec(result)); +} + +function run_test() { + + var sb = new Cu.Sandbox('http://www.example.org'); + sb.obj = {foo: 2}; + checkThrows('obj.foo = 3;', sb, /denied/); + Cu.evalInSandbox("var p = {__exposedProps__: {foo: 'rw'}};", sb); + sb.obj.__proto__ = sb.p; + checkThrows('obj.foo = 4;', sb, /__exposedProps__/); +} diff --git a/js/xpconnect/tests/unit/test_bug845201.js b/js/xpconnect/tests/unit/test_bug845201.js new file mode 100644 index 000000000..8b0d143be --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug845201.js @@ -0,0 +1,18 @@ +function sbTest() { + var threw = false; + try { + for (var x in Components) { } + do_check_true(false, "Shouldn't be able to enumerate Components"); + } catch(e) { + do_check_true(true, "Threw appropriately"); + threw = true; + } + do_check_true(threw, "Shouldn't have thrown uncatchable exception"); +} + +function run_test() { + var sb = Components.utils.Sandbox('http://www.example.com', { wantComponents: true }); + sb.do_check_true = do_check_true; + Components.utils.evalInSandbox(sbTest.toSource(), sb); + Components.utils.evalInSandbox('sbTest();', sb); +} diff --git a/js/xpconnect/tests/unit/test_bug845862.js b/js/xpconnect/tests/unit/test_bug845862.js new file mode 100644 index 000000000..650bdf3cd --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug845862.js @@ -0,0 +1,13 @@ +const Cu = Components.utils; + +function run_test() { + // We rely on the crazy "wantXrays:false also causes values return from the + // sandbox to be waived" behavior, because it's the simplest way to get + // waivers out of the sandbox (which has no native objects). :-( + var sb = new Cu.Sandbox('http://www.example.com', {wantXrays: false}); + Cu.evalInSandbox("this.foo = {}; Object.defineProperty(foo, 'bar', {get: function() {return {};}});", sb); + do_check_true(sb.foo != XPCNativeWrapper(sb.foo), "sb.foo is waived"); + var desc = Object.getOwnPropertyDescriptor(sb.foo, 'bar'); + var b = desc.get(); + do_check_true(b != XPCNativeWrapper(b), "results from accessor descriptors are waived"); +} diff --git a/js/xpconnect/tests/unit/test_bug849730.js b/js/xpconnect/tests/unit/test_bug849730.js new file mode 100644 index 000000000..8622b5bca --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug849730.js @@ -0,0 +1,7 @@ +const Cu = Components.utils; + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + sb.arr = [3, 4]; + do_check_true(Cu.evalInSandbox('!Array.isArray(arr);', sb)); +} diff --git a/js/xpconnect/tests/unit/test_bug851895.js b/js/xpconnect/tests/unit/test_bug851895.js new file mode 100644 index 000000000..7cab5b12d --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug851895.js @@ -0,0 +1,11 @@ +const Cu = Components.utils; + +function run_test() { + // Make sure Components.utils gets its |this| fixed up. + var isXrayWrapper = Components.utils.isXrayWrapper; + do_check_true(!isXrayWrapper({}), "Didn't throw"); + + // Even for classes without |this| fixup, make sure that we don't crash. + var isSuccessCode = Components.isSuccessCode; + try { isSuccessCode(Components.results.NS_OK); } catch (e) {}; +} diff --git a/js/xpconnect/tests/unit/test_bug853709.js b/js/xpconnect/tests/unit/test_bug853709.js new file mode 100644 index 000000000..c7e51757d --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug853709.js @@ -0,0 +1,32 @@ +const Cu = Components.utils; + +function setupChromeSandbox() { + this.chromeObj = {a: 2, __exposedProps__: {a: "rw", b: "rw"} }; + this.chromeArr = [4, 2, 1]; +} + +function checkDefineThrows(sb, obj, prop, desc) { + var result = Cu.evalInSandbox('(function() { try { Object.defineProperty(' + obj + ', "' + prop + '", ' + desc.toSource() + '); return "nothrow"; } catch (e) { return e.toString(); }})();', sb); + do_check_neq(result, 'nothrow'); + do_check_true(!!/denied/.exec(result)); + do_check_true(result.indexOf(prop) != -1); // Make sure the prop name is in the error message. +} + +function run_test() { + var chromeSB = new Cu.Sandbox(this); + var contentSB = new Cu.Sandbox('http://www.example.org'); + Cu.evalInSandbox('(' + setupChromeSandbox.toSource() + ')()', chromeSB); + contentSB.chromeObj = chromeSB.chromeObj; + contentSB.chromeArr = chromeSB.chromeArr; + + do_check_eq(Cu.evalInSandbox('chromeObj.a', contentSB), 2); + try { + Cu.evalInSandbox('chromeArr[1]', contentSB); + do_check_true(false); + } catch (e) { do_check_true(/denied|insecure/.test(e)); } + + checkDefineThrows(contentSB, 'chromeObj', 'a', {get: function() { return 2; }}); + checkDefineThrows(contentSB, 'chromeObj', 'a', {configurable: true, get: function() { return 2; }}); + checkDefineThrows(contentSB, 'chromeObj', 'b', {configurable: true, get: function() { return 2; }, set: function() {}}); + checkDefineThrows(contentSB, 'chromeArr', '1', {configurable: true, get: function() { return 2; }}); +} diff --git a/js/xpconnect/tests/unit/test_bug854558.js b/js/xpconnect/tests/unit/test_bug854558.js new file mode 100644 index 000000000..d60d23a5b --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug854558.js @@ -0,0 +1,11 @@ +const Cu = Components.utils; +function run_test() { + + var chromeSB = new Cu.Sandbox(this); + var contentSB = new Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox('this.foo = {a: 2}', chromeSB); + contentSB.foo = chromeSB.foo; + do_check_eq(Cu.evalInSandbox('foo.a', contentSB), undefined, "Default deny with no __exposedProps__"); + Cu.evalInSandbox('this.foo.__exposedProps__ = {a: "r"}', chromeSB); + do_check_eq(Cu.evalInSandbox('foo.a', contentSB), 2, "works with __exposedProps__"); +} diff --git a/js/xpconnect/tests/unit/test_bug856067.js b/js/xpconnect/tests/unit/test_bug856067.js new file mode 100644 index 000000000..b23cd45fb --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug856067.js @@ -0,0 +1,10 @@ +const Cu = Components.utils; + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + let w = Cu.evalInSandbox('var w = new WeakMap(); w.__proto__ = new Set(); w.foopy = 12; w', sb); + do_check_eq(Object.getPrototypeOf(w), sb.Object.prototype); + do_check_eq(Object.getOwnPropertyNames(w).length, 0); + do_check_eq(w.wrappedJSObject.foopy, 12); + do_check_eq(w.foopy, undefined); +} diff --git a/js/xpconnect/tests/unit/test_bug867486.js b/js/xpconnect/tests/unit/test_bug867486.js new file mode 100644 index 000000000..a807fa44e --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug867486.js @@ -0,0 +1,10 @@ +/* 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 Cu = Components.utils; + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com', { wantComponents: true } ); + do_check_false(Cu.evalInSandbox('"Components" in this', sb)); +} diff --git a/js/xpconnect/tests/unit/test_bug868675.js b/js/xpconnect/tests/unit/test_bug868675.js new file mode 100644 index 000000000..5aac9c7d6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug868675.js @@ -0,0 +1,25 @@ +const Cu = Components.utils; +function run_test() { + + // Make sure we don't throw for primitive values. + var result = "threw"; + try { result = XPCNativeWrapper.unwrap(2); } catch (e) {} + do_check_eq(result, 2); + result = "threw"; + try { result = XPCNativeWrapper(2); } catch (e) {} + do_check_eq(result, 2); + + // Make sure that we can waive on a non-Xrayable object, and that we preserve + // transitive waiving behavior. + var sb = new Cu.Sandbox('http://www.example.com', { wantGlobalProperties: ["XMLHttpRequest"] }); + Cu.evalInSandbox('this.xhr = new XMLHttpRequest();', sb); + Cu.evalInSandbox('this.jsobj = {mynative: xhr};', sb); + do_check_true(!Cu.isXrayWrapper(XPCNativeWrapper.unwrap(sb.xhr))); + do_check_true(Cu.isXrayWrapper(sb.jsobj.mynative)); + do_check_true(!Cu.isXrayWrapper(XPCNativeWrapper.unwrap(sb.jsobj).mynative)); + + // Test the new Cu API. + var waived = Cu.waiveXrays(sb.xhr); + do_check_true(!Cu.isXrayWrapper(waived)); + do_check_true(Cu.isXrayWrapper(Cu.unwaiveXrays(waived))); +} diff --git a/js/xpconnect/tests/unit/test_bug872772.js b/js/xpconnect/tests/unit/test_bug872772.js new file mode 100644 index 000000000..dc2e0db29 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug872772.js @@ -0,0 +1,43 @@ +const Cu = Components.utils; +function run_test() { + + // Make a content sandbox with an Xrayable object. + // NB: We use an nsEP here so that we can have access to Components, but still + // have Xray behavior from this scope. + var contentSB = new Cu.Sandbox(['http://www.google.com'], + { wantGlobalProperties: ["XMLHttpRequest"], wantComponents: true }); + + // Make an XHR in the content sandbox. + Cu.evalInSandbox('xhr = new XMLHttpRequest();', contentSB); + + // Make sure that waivers can be set as Xray expandos. + var xhr = contentSB.xhr; + do_check_true(Cu.isXrayWrapper(xhr)); + xhr.unwaivedExpando = xhr; + do_check_true(Cu.isXrayWrapper(xhr.unwaivedExpando)); + var waived = xhr.wrappedJSObject; + do_check_true(!Cu.isXrayWrapper(waived)); + xhr.waivedExpando = waived; + do_check_true(!Cu.isXrayWrapper(xhr.waivedExpando)); + + // Try the same thing for getters/setters, even though that's kind of + // contrived. + Cu.evalInSandbox('function f() {}', contentSB); + var f = contentSB.f; + var fWaiver = Cu.waiveXrays(f); + do_check_true(f != fWaiver); + do_check_true(Cu.unwaiveXrays(fWaiver) === f); + Object.defineProperty(xhr, 'waivedAccessors', {get: fWaiver, set: fWaiver}); + var desc = Object.getOwnPropertyDescriptor(xhr, 'waivedAccessors'); + do_check_true(desc.get === fWaiver); + do_check_true(desc.set === fWaiver); + + // Make sure we correctly handle same-compartment security wrappers. + var unwaivedC = contentSB.Components; + do_check_true(Cu.isXrayWrapper(unwaivedC)); + var waivedC = unwaivedC.wrappedJSObject; + do_check_true(waivedC && unwaivedC && (waivedC != unwaivedC)); + xhr.waivedC = waivedC; + do_check_true(xhr.waivedC === waivedC); + do_check_true(Cu.unwaiveXrays(xhr.waivedC) === unwaivedC); +} diff --git a/js/xpconnect/tests/unit/test_bug885800.js b/js/xpconnect/tests/unit/test_bug885800.js new file mode 100644 index 000000000..bb859141d --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug885800.js @@ -0,0 +1,13 @@ +const Cu = Components.utils; + +function run_test() { + var sb = new Cu.Sandbox('http://www.example.com'); + var obj = Cu.evalInSandbox('this.obj = {foo: 2}; obj', sb); + var chromeSb = new Cu.Sandbox(this); + chromeSb.objRef = obj; + do_check_eq(Cu.evalInSandbox('objRef.foo', chromeSb), 2); + Cu.nukeSandbox(sb); + do_check_true(Cu.isDeadWrapper(obj)); + // CCWs to nuked wrappers should be considered dead. + do_check_true(Cu.isDeadWrapper(chromeSb.objRef)); +} diff --git a/js/xpconnect/tests/unit/test_bug930091.js b/js/xpconnect/tests/unit/test_bug930091.js new file mode 100644 index 000000000..aa11d5db2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug930091.js @@ -0,0 +1,29 @@ +const Cu = Components.utils; + +function checkThrows(fn) { + try { + fn(); + ok(false, "Should have thrown"); + } catch (e) { + do_check_true(/denied|insecure/.test(e)); + } +} + +function run_test() { + var xosb = new Cu.Sandbox('http://www.example.org'); + var sb = new Cu.Sandbox('http://www.example.com'); + sb.do_check_true = do_check_true; + sb.fun = function() { ok(false, "Shouldn't ever reach me"); }; + sb.cow = { foopy: 2, __exposedProps__: { foopy: 'rw' } }; + sb.payload = Cu.evalInSandbox('new Object()', xosb); + Cu.evalInSandbox(checkThrows.toSource(), sb); + Cu.evalInSandbox('checkThrows(function() { fun(payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.prototype.call.call(fun, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.prototype.call.call(fun, null, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { new fun(payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { cow.foopy = payload; });', sb); + Cu.evalInSandbox('checkThrows(function() { Object.defineProperty(cow, "foopy", { value: payload }); });', sb); + // These fail for a different reason, .bind can't access the length/name property on the function. + Cu.evalInSandbox('checkThrows(function() { Function.bind.call(fun, null, payload); });', sb); + Cu.evalInSandbox('checkThrows(function() { Function.bind.call(fun, payload); });', sb); +} diff --git a/js/xpconnect/tests/unit/test_bug976151.js b/js/xpconnect/tests/unit/test_bug976151.js new file mode 100644 index 000000000..af4a22d3e --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug976151.js @@ -0,0 +1,24 @@ +/* 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 Cu = Components.utils; +function run_test() { + let unprivilegedSb = new Cu.Sandbox('http://www.example.com'); + function checkOpaqueWrapper(val) { + unprivilegedSb.prop = val; + try { + Cu.evalInSandbox('prop();', sb); + } catch (e) { + do_check_true(/denied|insecure|/.test(e)); + } + } + let xoSb = new Cu.Sandbox('http://www.example.net'); + let epSb = new Cu.Sandbox(['http://www.example.com']); + checkOpaqueWrapper(eval); + checkOpaqueWrapper(xoSb.eval); + checkOpaqueWrapper(epSb.eval); + checkOpaqueWrapper(Function); + checkOpaqueWrapper(xoSb.Function); + checkOpaqueWrapper(epSb.Function); +} diff --git a/js/xpconnect/tests/unit/test_bug_442086.js b/js/xpconnect/tests/unit/test_bug_442086.js new file mode 100644 index 000000000..8de098121 --- /dev/null +++ b/js/xpconnect/tests/unit/test_bug_442086.js @@ -0,0 +1,36 @@ +/* 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/. */ + +// Bug 442086 - XPConnect creates doubles without checking for +// the INT_FITS_IN_JSVAL case + +var types = [ + 'PRUint8', + 'PRUint16', + 'PRUint32', + 'PRUint64', + 'PRInt16', + 'PRInt32', + 'PRInt64', + 'float', + 'double' +]; + +function run_test() +{ + var i; + for (i = 0; i < types.length; i++) { + var name = types[i]; + var cls = Components.classes["@mozilla.org/supports-" + name + ";1"]; + var ifname = ("nsISupports" + name.charAt(0).toUpperCase() + + name.substring(1)); + var f = cls.createInstance(Components.interfaces[ifname]); + + f.data = 0; + switch (f.data) { + case 0: /*ok*/ break; + default: do_throw("FAILED - bug 442086 (type=" + name + ")"); + } + } +} diff --git a/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js new file mode 100644 index 000000000..aaf6e849b --- /dev/null +++ b/js/xpconnect/tests/unit/test_callFunctionWithAsyncStack.js @@ -0,0 +1,30 @@ +Components.utils.import("resource://gre/modules/Services.jsm"); + +function run_test() { + if (!Services.prefs.getBoolPref("javascript.options.asyncstack")) { + do_print("Async stacks are disabled."); + return; + } + + function getAsyncStack() { + return Components.stack; + } + + // asyncCause may contain non-ASCII characters. + let testAsyncCause = "Tes" + String.fromCharCode(355) + "String"; + + Components.utils.callFunctionWithAsyncStack(function asyncCallback() { + let stack = Components.stack; + + do_check_eq(stack.name, "asyncCallback"); + do_check_eq(stack.caller, null); + do_check_eq(stack.asyncCause, null); + + do_check_eq(stack.asyncCaller.name, "getAsyncStack"); + do_check_eq(stack.asyncCaller.asyncCause, testAsyncCause); + do_check_eq(stack.asyncCaller.asyncCaller, null); + + do_check_eq(stack.asyncCaller.caller.name, "run_test"); + do_check_eq(stack.asyncCaller.caller.asyncCause, null); + }, getAsyncStack(), testAsyncCause); +} diff --git a/js/xpconnect/tests/unit/test_classesByID_instanceof.js b/js/xpconnect/tests/unit/test_classesByID_instanceof.js new file mode 100644 index 000000000..b0acf8016 --- /dev/null +++ b/js/xpconnect/tests/unit/test_classesByID_instanceof.js @@ -0,0 +1,79 @@ +function testActual(SimpleURIClassByID) +{ + var simpleURI = + Components.classes["@mozilla.org/network/simple-uri;1"].createInstance(); + + do_check_eq(simpleURI instanceof SimpleURIClassByID, true); +} + +function testInherited(SimpleURIClassByID) +{ + var simpleURI = + Components.classes["@mozilla.org/network/simple-uri;1"].createInstance(); + + var inherited = Object.create(simpleURI); + + do_check_eq(inherited instanceof SimpleURIClassByID, true); +} + +function testInheritedCrossGlobal(SimpleURIClassByID) +{ + var simpleURI = + Components.classes["@mozilla.org/network/simple-uri;1"].createInstance(); + + var sb = new Components.utils.Sandbox(this, { wantComponents: true }); + var inheritedCross = sb.Object.create(simpleURI); + + do_check_eq(inheritedCross instanceof SimpleURIClassByID, true); +} + +function testCrossGlobalArbitraryGetPrototype(SimpleURIClassByID) +{ + var simpleURI = + Components.classes["@mozilla.org/network/simple-uri;1"].createInstance(); + + var sb = new Components.utils.Sandbox(this, { wantComponents: true }); + var firstLevel = Object.create(simpleURI); + + var obj = { shouldThrow: false }; + var secondLevel = + new sb.Proxy(Object.create(firstLevel), + { + getPrototypeOf: new sb.Function("obj", `return function(t) { + if (obj.shouldThrow) + throw 42; + return Reflect.getPrototypeOf(t); + };`)(obj) + }); + var thirdLevel = Object.create(secondLevel); + + obj.shouldThrow = true; + + var threw = false; + var err; + try + { + void (thirdLevel instanceof SimpleURIClassByID); + } + catch (e) + { + threw = true; + err = e; + } + + do_check_eq(threw, true); + do_check_eq(err, 42); + + obj.shouldThrow = false; + + do_check_eq(thirdLevel instanceof SimpleURIClassByID, true); +} + +function run_test() { + var SimpleURIClassByID = Components.classesByID["{e0da1d70-2f7b-11d3-8cd0-0060b0fc14a3}"]; + + testActual(SimpleURIClassByID); + testInherited(SimpleURIClassByID); + testInheritedCrossGlobal(SimpleURIClassByID); + testCrossGlobalArbitraryGetPrototype(SimpleURIClassByID); +} diff --git a/js/xpconnect/tests/unit/test_components.js b/js/xpconnect/tests/unit/test_components.js new file mode 100644 index 000000000..623a365c0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_components.js @@ -0,0 +1,54 @@ +const Cu = Components.utils; + +function run_test() { + var sb1 = Cu.Sandbox("http://www.blah.com"); + var sb2 = Cu.Sandbox("http://www.blah.com"); + var sb3 = Cu.Sandbox(this); + var sb4 = Cu.Sandbox("http://www.other.com"); + var rv; + + // Components is normally hidden from content on the XBL scope chain, but we + // expose it to content here to make sure that the security wrappers work + // regardless. + [sb1, sb2, sb4].forEach(function(x) { x.Components = Cu.getComponentsForScope(x); }); + + // non-chrome accessing chrome Components + sb1.C = Components; + checkThrows("C.utils", sb1); + checkThrows("C.classes", sb1); + + // non-chrome accessing own Components + do_check_eq(Cu.evalInSandbox("typeof Components.interfaces", sb1), 'object'); + do_check_eq(Cu.evalInSandbox("typeof Components.utils", sb1), 'undefined'); + do_check_eq(Cu.evalInSandbox("typeof Components.classes", sb1), 'undefined'); + + // Make sure an unprivileged Components is benign. + var C2 = Cu.evalInSandbox("Components", sb2); + var whitelist = ['interfaces', 'interfacesByID', 'results', 'isSuccessCode', 'QueryInterface']; + for (var prop in Components) { + do_print("Checking " + prop); + do_check_eq((prop in C2), whitelist.indexOf(prop) != -1); + } + + // non-chrome same origin + sb1.C2 = C2; + do_check_eq(Cu.evalInSandbox("typeof C2.interfaces", sb1), 'object'); + do_check_eq(Cu.evalInSandbox("typeof C2.utils", sb1), 'undefined'); + do_check_eq(Cu.evalInSandbox("typeof C2.classes", sb1), 'undefined'); + + // chrome accessing chrome + sb3.C = Components; + rv = Cu.evalInSandbox("C.utils", sb3); + do_check_eq(rv, Cu); + + // non-chrome cross origin + sb4.C2 = C2; + checkThrows("C2.interfaces", sb4); + checkThrows("C2.utils", sb4); + checkThrows("C2.classes", sb4); +} + +function checkThrows(expression, sb) { + var result = Cu.evalInSandbox('(function() { try { ' + expression + '; return "allowed"; } catch (e) { return e.toString(); }})();', sb); + do_check_true(!!/denied/.exec(result)); +} diff --git a/js/xpconnect/tests/unit/test_crypto.js b/js/xpconnect/tests/unit/test_crypto.js new file mode 100644 index 000000000..90c0f9ed2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_crypto.js @@ -0,0 +1,28 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let Cu = Components.utils; + let sb = new Cu.Sandbox('https://www.example.com', + { wantGlobalProperties: + ["crypto", "TextEncoder", "TextDecoder"] + }); + sb.ok = ok; + Cu.evalInSandbox('ok(this.crypto);', sb); + Cu.evalInSandbox('ok(this.crypto.subtle);', sb); + sb.do_check_eq = do_check_eq; + let innerPromise = new Promise(r => (sb.test_done = r)); + Cu.evalInSandbox('crypto.subtle.digest("SHA-256", ' + + ' new TextEncoder("utf-8").encode("abc"))' + + ' .then(h => do_check_eq(new Uint16Array(h)[0], 30906))' + + ' .then(test_done);', sb); + + Cu.importGlobalProperties(["crypto"]); + ok(crypto); + ok(crypto.subtle); + let outerPromise = crypto.subtle.digest("SHA-256", new TextEncoder("utf-8").encode("abc")) + .then(h => do_check_eq(new Uint16Array(h)[0], 30906)); + + do_test_pending(); + Promise.all([innerPromise, outerPromise]).then(() => do_test_finished()); +} diff --git a/js/xpconnect/tests/unit/test_css.js b/js/xpconnect/tests/unit/test_css.js new file mode 100644 index 000000000..5af9d74b6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_css.js @@ -0,0 +1,10 @@ +function run_test() { + var Cu = Components.utils; + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["CSS"] }); + sb.do_check_eq = do_check_eq; + Cu.evalInSandbox('do_check_eq(CSS.escape("$"), "\\\\$");', + sb); + Cu.importGlobalProperties(["CSS"]); + do_check_eq(CSS.escape("$"), "\\$"); +} diff --git a/js/xpconnect/tests/unit/test_deepFreezeClone.js b/js/xpconnect/tests/unit/test_deepFreezeClone.js new file mode 100644 index 000000000..67ea4e66d --- /dev/null +++ b/js/xpconnect/tests/unit/test_deepFreezeClone.js @@ -0,0 +1,33 @@ +const Cu = Components.utils; + +function checkThrows(f, rgxp) { try { f(); do_check_false(); } catch (e) { do_check_true(rgxp.test(e)); } } + +var o = { foo: 42, bar : { tick: 'tock' } }; +function checkClone(clone, frozen) { + var waived = Cu.waiveXrays(clone); + function touchFoo() { "use strict"; waived.foo = 12; do_check_eq(waived.foo, 12); } + function touchBar() { "use strict"; waived.bar.tick = 'tack'; do_check_eq(waived.bar.tick, 'tack'); } + function addProp() { "use strict"; waived.newProp = 100; do_check_eq(waived.newProp, 100); } + if (!frozen) { + touchFoo(); + touchBar(); + addProp(); + } else { + checkThrows(touchFoo, /read-only/); + checkThrows(touchBar, /read-only/); + checkThrows(addProp, /extensible/); + } + + var desc = Object.getOwnPropertyDescriptor(waived, 'foo'); + do_check_eq(desc.writable, !frozen); + do_check_eq(desc.configurable, !frozen); + desc = Object.getOwnPropertyDescriptor(waived.bar, 'tick'); + do_check_eq(desc.writable, !frozen); + do_check_eq(desc.configurable, !frozen); +} + +function run_test() { + var sb = new Cu.Sandbox(null); + checkClone(Cu.waiveXrays(Cu.cloneInto(o, sb)), false); + checkClone(Cu.cloneInto(o, sb, { deepFreeze: true }), true); +} diff --git a/js/xpconnect/tests/unit/test_exportFunction.js b/js/xpconnect/tests/unit/test_exportFunction.js new file mode 100644 index 000000000..830816342 --- /dev/null +++ b/js/xpconnect/tests/unit/test_exportFunction.js @@ -0,0 +1,150 @@ +function run_test() { + var Cu = Components.utils; + var epsb = new Cu.Sandbox(["http://example.com", "http://example.org"], { wantExportHelpers: true }); + var subsb = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + var subsb2 = new Cu.Sandbox("http://example.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + var xorigsb = new Cu.Sandbox("http://test.com", { wantGlobalProperties: ["XMLHttpRequest"] }); + + epsb.subsb = subsb; + epsb.xorigsb = xorigsb; + epsb.do_check_true = do_check_true; + epsb.do_check_eq = do_check_eq; + subsb.do_check_true = do_check_true; + + // Exporting should work if prinicipal of the source sandbox + // subsumes the principal of the target sandbox. + Cu.evalInSandbox("(" + function() { + var wasCalled = false; + this.funToExport = function(expectedThis, a, obj, native, mixed, callback) { + do_check_eq(a, 42); + do_check_eq(obj, subsb.tobecloned); + do_check_eq(obj.cloned, "cloned"); + do_check_eq(native, subsb.native); + do_check_eq(expectedThis, this); + do_check_eq(mixed.xrayed, subsb.xrayed); + do_check_eq(mixed.xrayed2, subsb.xrayed2); + if (typeof callback == 'function') { + do_check_eq(typeof subsb.callback, 'function'); + do_check_eq(callback, subsb.callback); + callback(); + } + wasCalled = true; + }; + this.checkIfCalled = function() { + do_check_true(wasCalled); + wasCalled = false; + } + exportFunction(funToExport, subsb, { defineAs: "imported", allowCallbacks: true }); + exportFunction((x) => x, subsb, { defineAs: "echoAllowXO", allowCallbacks: true, allowCrossOriginArguments: true }); + }.toSource() + ")()", epsb); + + subsb.xrayed = Cu.evalInSandbox("(" + function () { + return new XMLHttpRequest(); + }.toSource() + ")()", subsb2); + + // Exported function should be able to be call from the + // target sandbox. Native arguments should be just wrapped + // every other argument should be cloned. + Cu.evalInSandbox("(" + function () { + native = new XMLHttpRequest(); + xrayed2 = XPCNativeWrapper(new XMLHttpRequest()); + mixed = { xrayed: xrayed, xrayed2: xrayed2 }; + tobecloned = { cloned: "cloned" }; + invokedCallback = false; + callback = function() { invokedCallback = true; }; + imported(this, 42, tobecloned, native, mixed, callback); + do_check_true(invokedCallback); + }.toSource() + ")()", subsb); + + // Invoking an exported function with cross-origin arguments should throw. + subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb); + try { + Cu.evalInSandbox('imported(this, xoNative)', subsb); + do_check_true(false); + } catch (e) { + do_check_true(/denied|insecure/.test(e)); + } + + // Callers can opt-out of the above. + subsb.xoNative = Cu.evalInSandbox('new XMLHttpRequest()', xorigsb); + try { + do_check_eq(Cu.evalInSandbox('echoAllowXO(xoNative)', subsb), subsb.xoNative); + do_check_true(true); + } catch (e) { + do_check_true(false); + } + + // Apply should work and |this| should carry over appropriately. + Cu.evalInSandbox("(" + function() { + var someThis = {}; + imported.apply(someThis, [someThis, 42, tobecloned, native, mixed]); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // Exporting should throw if principal of the source sandbox does + // not subsume the principal of the target. + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(function() {}, this.xorigsb, { defineAs: "denied" }); + do_check_true(false); + } catch (e) { + do_check_true(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); + + // Exporting should throw if the principal of the source sandbox does + // not subsume the principal of the function. + epsb.xo_function = new xorigsb.Function(); + Cu.evalInSandbox("(" + function() { + try{ + exportFunction(xo_function, this.subsb, { defineAs: "denied" }); + do_check_true(false); + } catch (e) { + dump('Exception: ' + e); + do_check_true(e.toString().indexOf('Permission denied') > -1); + } + }.toSource() + ")()", epsb); + + // Let's create an object in the target scope and add privileged + // function to it as a property. + Cu.evalInSandbox("(" + function() { + var newContentObject = createObjectIn(subsb, { defineAs: "importedObject" }); + exportFunction(funToExport, newContentObject, { defineAs: "privMethod" }); + }.toSource() + ")()", epsb); + + Cu.evalInSandbox("(" + function () { + importedObject.privMethod(importedObject, 42, tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + // exportFunction and createObjectIn should be available from Cu too. + var newContentObject = Cu.createObjectIn(subsb, { defineAs: "importedObject2" }); + var wasCalled = false; + Cu.exportFunction(function(arg) { wasCalled = arg.wasCalled; }, + newContentObject, { defineAs: "privMethod" }); + + Cu.evalInSandbox("(" + function () { + importedObject2.privMethod({wasCalled: true}); + }.toSource() + ")()", subsb); + + // 3rd argument of exportFunction should be optional. + Cu.evalInSandbox("(" + function() { + subsb.imported2 = exportFunction(funToExport, subsb); + }.toSource() + ")()", epsb); + + Cu.evalInSandbox("(" + function () { + imported2(this, 42, tobecloned, native, mixed); + }.toSource() + ")()", subsb); + + Cu.evalInSandbox("(" + function() { + checkIfCalled(); + }.toSource() + ")()", epsb); + + do_check_true(wasCalled, true); +} diff --git a/js/xpconnect/tests/unit/test_file.js b/js/xpconnect/tests/unit/test_file.js new file mode 100644 index 000000000..a06a4208d --- /dev/null +++ b/js/xpconnect/tests/unit/test_file.js @@ -0,0 +1,15 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + do_load_manifest("component-file.manifest"); + const contractID = "@mozilla.org/tests/component-file;1"; + do_check_true(contractID in Components.classes); + var foo = Components.classes[contractID] + .createInstance(Components.interfaces.nsIClassInfo); + do_check_true(Boolean(foo)); + do_check_true(foo.contractID == contractID); + do_check_true(!!foo.wrappedJSObject); + do_check_true(foo.wrappedJSObject.doTest()); +} diff --git a/js/xpconnect/tests/unit/test_file2.js b/js/xpconnect/tests/unit/test_file2.js new file mode 100644 index 000000000..6ebe7b822 --- /dev/null +++ b/js/xpconnect/tests/unit/test_file2.js @@ -0,0 +1,62 @@ +/* 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/. */ + +Components.utils.importGlobalProperties(['File']); + +const Ci = Components.interfaces; + +function run_test() { + // throw if anything goes wrong + + // find the current directory path + var file = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + file.append("xpcshell.ini"); + + // should be able to construct a file + var f1 = File.createFromFileName(file.path); + // and with nsIFiles + var f2 = File.createFromNsIFile(file); + + // do some tests + do_check_true(f1 instanceof File, "Should be a DOM File"); + do_check_true(f2 instanceof File, "Should be a DOM File"); + + do_check_true(f1.name == "xpcshell.ini", "Should be the right file"); + do_check_true(f2.name == "xpcshell.ini", "Should be the right file"); + + do_check_true(f1.type == "", "Should be the right type"); + do_check_true(f2.type == "", "Should be the right type"); + + var threw = false; + try { + // Needs a ctor argument + var f7 = File(); + } catch (e) { + threw = true; + } + do_check_true(threw, "No ctor arguments should throw"); + + var threw = false; + try { + // Needs a valid ctor argument + var f7 = File(Date(132131532)); + } catch (e) { + threw = true; + } + do_check_true(threw, "Passing a random object should fail"); + + var threw = false + try { + // Directories fail + var dir = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Ci.nsIProperties) + .get("CurWorkD", Ci.nsIFile); + var f7 = File.createFromNsIFile(dir) + } catch (e) { + threw = true; + } + do_check_true(threw, "Can't create a File object for a directory"); +} diff --git a/js/xpconnect/tests/unit/test_fileReader.js b/js/xpconnect/tests/unit/test_fileReader.js new file mode 100644 index 000000000..cd4859301 --- /dev/null +++ b/js/xpconnect/tests/unit/test_fileReader.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + var Cu = Components.utils; + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["FileReader"] }); + sb.do_check_true = do_check_true; + Cu.evalInSandbox('do_check_true((new FileReader()) instanceof FileReader);', + sb); + Cu.importGlobalProperties(["FileReader"]); + do_check_true((new FileReader()) instanceof FileReader); +} diff --git a/js/xpconnect/tests/unit/test_getObjectPrincipal.js b/js/xpconnect/tests/unit/test_getObjectPrincipal.js new file mode 100644 index 000000000..509c9a5d4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_getObjectPrincipal.js @@ -0,0 +1,11 @@ +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +function run_test() { + var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager); + do_check_true(secMan.isSystemPrincipal(Cu.getObjectPrincipal({}))); + var sb = new Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox('var obj = { foo: 42 };', sb); + do_check_eq(Cu.getObjectPrincipal(sb.obj).origin, 'http://www.example.com'); +} diff --git a/js/xpconnect/tests/unit/test_import.js b/js/xpconnect/tests/unit/test_import.js new file mode 100644 index 000000000..5475c9011 --- /dev/null +++ b/js/xpconnect/tests/unit/test_import.js @@ -0,0 +1,91 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope = {}; + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", scope); + do_check_eq(typeof(scope.XPCOMUtils), "object"); + do_check_eq(typeof(scope.XPCOMUtils.generateNSGetFactory), "function"); + + // access module's global object directly without importing any + // symbols + var module = Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", + null); + do_check_eq(typeof(XPCOMUtils), "undefined"); + do_check_eq(typeof(module), "object"); + do_check_eq(typeof(module.XPCOMUtils), "object"); + do_check_eq(typeof(module.XPCOMUtils.generateNSGetFactory), "function"); + do_check_true(scope.XPCOMUtils == module.XPCOMUtils); + + // import symbols to our global object + do_check_eq(typeof(Components.utils.import), "function"); + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + do_check_eq(typeof(XPCOMUtils), "object"); + do_check_eq(typeof(XPCOMUtils.generateNSGetFactory), "function"); + + // try on a new object + var scope2 = {}; + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", scope2); + do_check_eq(typeof(scope2.XPCOMUtils), "object"); + do_check_eq(typeof(scope2.XPCOMUtils.generateNSGetFactory), "function"); + + do_check_true(scope2.XPCOMUtils == scope.XPCOMUtils); + + // try on a new object using the resolved URL + var res = Components.classes["@mozilla.org/network/protocol;1?name=resource"] + .getService(Components.interfaces.nsIResProtocolHandler); + var resURI = res.newURI("resource://gre/modules/XPCOMUtils.jsm", null, null); + dump("resURI: " + resURI + "\n"); + var filePath = res.resolveURI(resURI); + var scope3 = {}; + Components.utils.import(filePath, scope3); + do_check_eq(typeof(scope3.XPCOMUtils), "object"); + do_check_eq(typeof(scope3.XPCOMUtils.generateNSGetFactory), "function"); + + do_check_true(scope3.XPCOMUtils == scope.XPCOMUtils); + + // make sure we throw when the second arg is bogus + var didThrow = false; + try { + Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", "wrong"); + } catch (ex) { + print("exception (expected): " + ex); + didThrow = true; + } + do_check_true(didThrow); + + // try to create a component + do_load_manifest("component_import.manifest"); + const contractID = "@mozilla.org/tests/module-importer;"; + do_check_true((contractID + "1") in Components.classes); + var foo = Components.classes[contractID + "1"] + .createInstance(Components.interfaces.nsIClassInfo); + do_check_true(Boolean(foo)); + do_check_true(foo.contractID == contractID + "1"); + // XXX the following check succeeds only if the test component wasn't + // already registered. Need to figure out a way to force registration + // (to manually force it, delete compreg.dat before running the test) + // do_check_true(foo.wrappedJSObject.postRegisterCalled); + + // Call getInterfaces to test line numbers in JS components. But as long as + // we're doing that, why not test what it returns too? + // Kind of odd that this is not returning an array containing the + // number... Or for that matter not returning an array containing an object? + var interfaces = foo.getInterfaces({}); + do_check_eq(interfaces, Components.interfaces.nsIClassInfo.number); + + // try to create a component by CID + const cid = "{6b933fe6-6eba-4622-ac86-e4f654f1b474}"; + do_check_true(cid in Components.classesByID); + foo = Components.classesByID[cid] + .createInstance(Components.interfaces.nsIClassInfo); + do_check_true(foo.contractID == contractID + "1"); + + // try to create another component which doesn't directly implement QI + do_check_true((contractID + "2") in Components.classes); + var bar = Components.classes[contractID + "2"] + .createInstance(Components.interfaces.nsIClassInfo); + do_check_true(Boolean(bar)); + do_check_true(bar.contractID == contractID + "2"); +} diff --git a/js/xpconnect/tests/unit/test_import_fail.js b/js/xpconnect/tests/unit/test_import_fail.js new file mode 100644 index 000000000..d297fa01d --- /dev/null +++ b/js/xpconnect/tests/unit/test_import_fail.js @@ -0,0 +1,10 @@ +function run_test() +{ + try { + Components.utils.import("resource://test/importer.jsm"); + do_check_true(false, "import should not succeed."); + } catch (x) { + do_check_neq(x.fileName.indexOf("syntax_error.jsm"), -1); + do_check_eq(x.lineNumber, 1); + } +} \ No newline at end of file diff --git a/js/xpconnect/tests/unit/test_interposition.js b/js/xpconnect/tests/unit/test_interposition.js new file mode 100644 index 000000000..f755bbd7d --- /dev/null +++ b/js/xpconnect/tests/unit/test_interposition.js @@ -0,0 +1,165 @@ +const Cu = Components.utils; +const Ci = Components.interfaces; +const Cc = Components.classes; + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +const ADDONID = "bogus-addon@mozilla.org"; + +let gExpectedProp; +function expectAccess(prop, f) +{ + gExpectedProp = prop; + f(); + do_check_eq(gExpectedProp, undefined); +} + +let getter_run = false; +function test_getter() +{ + do_check_eq(getter_run, false); + getter_run = true; + return 200; +} + +let setter_run = false; +function test_setter(v) +{ + do_check_eq(setter_run, false); + do_check_eq(v, 300); + setter_run = true; +} + +let TestInterposition = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIAddonInterposition, + Ci.nsISupportsWeakReference]), + + getWhitelist: function() { + return ["abcxyz", "utils", "dataprop", "getterprop", "setterprop", + "objprop", "defineprop", "configurableprop"]; + }, + + interposeProperty: function(addonId, target, iid, prop) { + do_check_eq(addonId, ADDONID); + do_check_eq(gExpectedProp, prop); + gExpectedProp = undefined; + + if (prop == "dataprop") { + return { configurable: false, enumerable: true, writable: false, value: 100 }; + } else if (prop == "getterprop") { + return { configurable: false, enumerable: true, get: test_getter }; + } else if (prop == "setterprop") { + return { configurable: false, enumerable: true, get: test_getter, set: test_setter }; + } else if (prop == "utils") { + do_check_eq(iid, Ci.nsIXPCComponents.number); + return null; + } else if (prop == "objprop") { + gExpectedProp = "objprop"; // allow recursive access here + return { configurable: false, enumerable: true, writable: false, value: { objprop: 1 } }; + } else if (prop == "configurableprop") { + return { configurable: true, enumerable: true, writable: false, value: 10 }; + } + + return null; + }, + + interposeCall: function(addonId, originalFunc, originalThis, args) { + do_check_eq(addonId, ADDONID); + args.splice(0, 0, addonId); + return originalFunc.apply(originalThis, args); + } +}; + +function run_test() +{ + Cu.setAddonInterposition(ADDONID, TestInterposition); + + Cu.importGlobalProperties(["XMLHttpRequest"]); + + let sandbox = Cu.Sandbox(this, {addonId: ADDONID}); + sandbox.outerObj = new XMLHttpRequest(); + + expectAccess("abcxyz", () => { + Cu.evalInSandbox("outerObj.abcxyz = 12;", sandbox); + }); + + expectAccess("utils", () => { + Cu.evalInSandbox("Components.utils;", sandbox); + }); + + expectAccess("dataprop", () => { + do_check_eq(Cu.evalInSandbox("outerObj.dataprop;", sandbox), 100); + }); + + expectAccess("dataprop", () => { + try { + Cu.evalInSandbox("'use strict'; outerObj.dataprop = 400;", sandbox); + do_check_true(false); // it should throw + } catch (e) {} + }); + + expectAccess("objprop", () => { + Cu.evalInSandbox("outerObj.objprop.objprop;", sandbox); + gExpectedProp = undefined; + }); + + expectAccess("getterprop", () => { + do_check_eq(Cu.evalInSandbox("outerObj.getterprop;", sandbox), 200); + do_check_eq(getter_run, true); + getter_run = false; + }); + + expectAccess("getterprop", () => { + try { + Cu.evalInSandbox("'use strict'; outerObj.getterprop = 400;", sandbox); + do_check_true(false); // it should throw + } catch (e) {} + do_check_eq(getter_run, false); + }); + + expectAccess("setterprop", () => { + do_check_eq(Cu.evalInSandbox("outerObj.setterprop;", sandbox), 200); + do_check_eq(getter_run, true); + getter_run = false; + do_check_eq(setter_run, false); + }); + + expectAccess("setterprop", () => { + Cu.evalInSandbox("'use strict'; outerObj.setterprop = 300;", sandbox); + do_check_eq(getter_run, false); + do_check_eq(setter_run, true); + setter_run = false; + }); + + // Make sure that calling Object.defineProperty succeeds as long as + // we're not interposing on that property. + expectAccess("defineprop", () => { + Cu.evalInSandbox("'use strict'; Object.defineProperty(outerObj, 'defineprop', {configurable:true, enumerable:true, writable:true, value:10});", sandbox); + }); + + // Related to above: make sure we can delete those properties too. + expectAccess("defineprop", () => { + Cu.evalInSandbox("'use strict'; delete outerObj.defineprop;", sandbox); + }); + + // Make sure we get configurable=false even if the interposition + // sets it to true. + expectAccess("configurableprop", () => { + let desc = Cu.evalInSandbox("Object.getOwnPropertyDescriptor(outerObj, 'configurableprop')", sandbox); + do_check_eq(desc.configurable, false); + }); + + let moduleScope = Cu.Sandbox(this); + moduleScope.ADDONID = ADDONID; + moduleScope.do_check_eq = do_check_eq; + function funToIntercept(addonId) { + do_check_eq(addonId, ADDONID); + counter++; + } + sandbox.moduleFunction = Cu.evalInSandbox(funToIntercept.toSource() + "; funToIntercept", moduleScope); + Cu.evalInSandbox("var counter = 0;", moduleScope); + Cu.evalInSandbox("Components.utils.setAddonCallInterposition(this);", moduleScope); + Cu.evalInSandbox("moduleFunction()", sandbox); + do_check_eq(Cu.evalInSandbox("counter", moduleScope), 1); + Cu.setAddonInterposition(ADDONID, null); +} diff --git a/js/xpconnect/tests/unit/test_isModuleLoaded.js b/js/xpconnect/tests/unit/test_isModuleLoaded.js new file mode 100644 index 000000000..8b1f9eb3d --- /dev/null +++ b/js/xpconnect/tests/unit/test_isModuleLoaded.js @@ -0,0 +1,33 @@ +const Cu = Components.utils; + +function run_test() { + // Existing module. + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + Cu.import("resource://gre/modules/ISO8601DateUtils.jsm"); + do_check_true(Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"), + "isModuleLoaded returned true after loading that module"); + Cu.unload("resource://gre/modules/ISO8601DateUtils.jsm"); + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils.jsm"), + "isModuleLoaded returned false after unloading that module"); + + // Non-existing module + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + try { + Cu.import("resource://gre/modules/ISO8601DateUtils1.jsm"); + do_check_true(false, + "Should have thrown while trying to load a non existing file"); + } catch (ex) {} + do_check_true(!Cu.isModuleLoaded("resource://gre/modules/ISO8601DateUtils1.jsm"), + "isModuleLoaded returned correct value for non-loaded module"); + + // incorrect url + try { + Cu.isModuleLoaded("resource://modules/ISO8601DateUtils1.jsm"); + do_check_true(false, + "Should have thrown while trying to load a non existing file"); + } catch (ex) { + do_check_true(true, "isModuleLoaded threw an exception while loading incorrect uri"); + } +} diff --git a/js/xpconnect/tests/unit/test_isProxy.js b/js/xpconnect/tests/unit/test_isProxy.js new file mode 100644 index 000000000..7cc526226 --- /dev/null +++ b/js/xpconnect/tests/unit/test_isProxy.js @@ -0,0 +1,29 @@ +function run_test() { + var Cu = Components.utils; + + var handler = { + get: function(target, name){ + return name in target? + target[name] : + 37; + } + }; + + var p = new Proxy({}, handler); + do_check_true(Cu.isProxy(p)); + do_check_false(Cu.isProxy({})); + do_check_false(Cu.isProxy(42)); + + sb = new Cu.Sandbox(this, + { wantExportHelpers: true }); + + do_check_false(Cu.isProxy(sb)); + + sb.do_check_true = do_check_true; + sb.do_check_false = do_check_false; + sb.p = p; + Cu.evalInSandbox('do_check_true(isProxy(p));' + + 'do_check_false(isProxy({}));' + + 'do_check_false(isProxy(42));', + sb); +} diff --git a/js/xpconnect/tests/unit/test_js_weak_references.js b/js/xpconnect/tests/unit/test_js_weak_references.js new file mode 100644 index 000000000..837fdb38b --- /dev/null +++ b/js/xpconnect/tests/unit/test_js_weak_references.js @@ -0,0 +1,45 @@ +/* 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/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=317304 */ + +function run_test() +{ + // Bug 712649: Calling getWeakReference(null) should work. + try { + var nullWeak = Components.utils.getWeakReference(null); + do_check_true(nullWeak.get() === null); + } catch (e) { + do_check_true(false); + } + + var obj = { num: 5, str: 'foo' }; + var weak = Components.utils.getWeakReference(obj); + + do_check_true(weak.get() === obj); + do_check_true(weak.get().num == 5); + do_check_true(weak.get().str == 'foo'); + + // Force garbage collection + Components.utils.forceGC(); + + // obj still references the object, so it should still be accessible via weak + do_check_true(weak.get() === obj); + do_check_true(weak.get().num == 5); + do_check_true(weak.get().str == 'foo'); + + // Clear obj's reference to the object and force garbage collection. To make + // sure that there are no instances of obj stored in the registers or on the + // native stack and the conservative GC would not find it we force the same + // code paths that we used for the initial allocation. + obj = { num: 6, str: 'foo2' }; + var weak2 = Components.utils.getWeakReference(obj); + do_check_true(weak2.get() === obj); + + Components.utils.forceGC(); + + // The object should have been garbage collected and so should no longer be + // accessible via weak + do_check_true(weak.get() === null); +} diff --git a/js/xpconnect/tests/unit/test_localeCompare.js b/js/xpconnect/tests/unit/test_localeCompare.js new file mode 100644 index 000000000..08560d1ae --- /dev/null +++ b/js/xpconnect/tests/unit/test_localeCompare.js @@ -0,0 +1,6 @@ +function run_test() { + do_check_true("C".localeCompare("D") < 0); + do_check_true("D".localeCompare("C") > 0); + do_check_true("\u010C".localeCompare("D") < 0); + do_check_true("D".localeCompare("\u010C") > 0); +} diff --git a/js/xpconnect/tests/unit/test_nuke_sandbox.js b/js/xpconnect/tests/unit/test_nuke_sandbox.js new file mode 100644 index 000000000..a4dd25498 --- /dev/null +++ b/js/xpconnect/tests/unit/test_nuke_sandbox.js @@ -0,0 +1,29 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=769273 */ + +function run_test() +{ + var sb = Components.utils.Sandbox("http://www.blah.com"); + sb.prop = "prop" + var refToObjFromSb = Components.utils.evalInSandbox("var a = {prop2:'prop2'}; a", sb); + Components.utils.nukeSandbox(sb); + do_check_true(Components.utils.isDeadWrapper(sb), "sb should be dead"); + try{ + sb.prop; + do_check_true(false); + } catch (e) { + do_check_true(e.toString().indexOf("can't access dead object") > -1); + } + + Components.utils.isDeadWrapper(refToObjFromSb, "ref to object from sb should be dead"); + try{ + refToObjFromSb.prop2; + do_check_true(false); + } catch (e) { + do_check_true(e.toString().indexOf("can't access dead object") > -1); + } + +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-01.js b/js/xpconnect/tests/unit/test_onGarbageCollection-01.js new file mode 100644 index 000000000..138f699f9 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-01.js @@ -0,0 +1,64 @@ +// Test basic usage of onGarbageCollection + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +const NUM_SLICES = root.NUM_SLICES = 10; + +let fired = false; +let slicesFound = 0; + +dbg.memory.onGarbageCollection = data => { + fired = true; + + print("Got onGarbageCollection: " + JSON.stringify(data, null, 2)); + + equal(typeof data.reason, "string"); + equal(typeof data.nonincrementalReason == "string" || data.nonincrementalReason === null, + true); + + let lastStartTimestamp = 0; + for (let i = 0; i < data.collections.length; i++) { + let slice = data.collections[i]; + + equal(slice.startTimestamp >= lastStartTimestamp, true); + equal(slice.startTimestamp <= slice.endTimestamp, true); + + lastStartTimestamp = slice.startTimestamp; + } + + equal(data.collections.length >= 1, true); + slicesFound += data.collections.length; +} + +function run_test() { + do_test_pending(); + + root.eval( + ` + this.allocs = []; + + // GC slices + for (var i = 0; i < NUM_SLICES; i++) { + this.allocs.push({}); + gcslice(); + } + + // Full GC + this.allocs.push({}); + gc(); + ` + ); + + executeSoon(() => { + equal(fired, true, "The GC hook should have fired at least once"); + + // NUM_SLICES + 1 full gc + however many were triggered naturally (due to + // whatever zealousness setting). + print("Found " + slicesFound + " slices"); + equal(slicesFound >= NUM_SLICES + 1, true); + + do_test_finished(); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-02.js b/js/xpconnect/tests/unit/test_onGarbageCollection-02.js new file mode 100644 index 000000000..a8bf22c67 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-02.js @@ -0,0 +1,94 @@ +// Test multiple debuggers, GCs, and zones interacting with each other. +// +// Note: when observing both globals, but GC'ing in only one, we don't test that +// we *didn't* GC in the other zone because GCs are finicky and unreliable. That +// used to work when this was a jit-test, but in the process of migrating to +// xpcshell, we lost some amount of reliability and determinism. + +const root1 = newGlobal(); +const dbg1 = new Debugger(); +dbg1.addDebuggee(root1) + +const root2 = newGlobal(); +const dbg2 = new Debugger(); +dbg2.addDebuggee(root2) + +let fired1 = false; +let fired2 = false; +dbg1.memory.onGarbageCollection = _ => fired1 = true; +dbg2.memory.onGarbageCollection = _ => fired2 = true; + +function reset() { + fired1 = false; + fired2 = false; +} + +function run_test() { + do_test_pending(); + + gc(); + executeSoon(() => { + reset(); + + // GC 1 only + root1.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + + // GC 2 only + reset(); + root2.eval(`gc(this)`); + executeSoon(() => { + equal(fired2, true); + + // Full GC + reset(); + gc(); + executeSoon(() => { + equal(fired1, true); + equal(fired2, true); + + // Full GC with no debuggees + reset(); + dbg1.removeAllDebuggees(); + dbg2.removeAllDebuggees(); + gc(); + executeSoon(() => { + equal(fired1, false); + equal(fired2, false); + + // One debugger with multiple debuggees in different zones. + + dbg1.addDebuggee(root1); + dbg1.addDebuggee(root2); + + // Just debuggee 1 + reset(); + root1.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + + // Just debuggee 2 + reset(); + root2.eval(`gc(this)`); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + + // All debuggees + reset(); + gc(); + executeSoon(() => { + equal(fired1, true); + equal(fired2, false); + do_test_finished(); + }); + }); + }); + }); + }); + }); + }); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-03.js b/js/xpconnect/tests/unit/test_onGarbageCollection-03.js new file mode 100644 index 000000000..1c7f158a7 --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-03.js @@ -0,0 +1,35 @@ +// Test that the onGarbageCollection hook is not reentrant. + + +function run_test() { + do_test_pending(); + + const root = newGlobal(); + const dbg = new Debugger(); + const wrappedRoot = dbg.addDebuggee(root) + + let fired = true; + let depth = 0; + + dbg.memory.onGarbageCollection = _ => { + fired = true; + + equal(depth, 0); + depth++; + try { + root.eval(`gc()`); + } finally { + equal(depth, 1); + depth--; + } + } + + root.eval(`gc()`); + + executeSoon(() => { + ok(fired); + equal(depth, 0); + dbg.memory.onGarbageCollection = undefined; + do_test_finished(); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-04.js b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js new file mode 100644 index 000000000..a7c71b82a --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-04.js @@ -0,0 +1,67 @@ +// Test that the onGarbageCollection reentrancy guard is on a per Debugger +// basis. That is if our first Debugger is observing our second Debugger's +// compartment, and this second Debugger triggers a GC inside its +// onGarbageCollection hook, the first Debugger's onGarbageCollection hook is +// still called. +// +// This is the scenario we are setting up: top level debugging the `debuggeree` +// global, which is debugging the `debuggee` global. Then, we trigger the +// following events: +// +// debuggee gc +// | +// V +// debuggeree's onGarbageCollection +// | +// V +// debuggeree gc +// | +// V +// top level onGarbageCollection +// +// Note that the top level's onGarbageCollection hook should be fired, at the +// same time that we are preventing reentrancy into debuggeree's +// onGarbageCollection hook. + +function run_test() { + do_test_pending(); + + const debuggeree = newGlobal(); + const debuggee = debuggeree.debuggee = newGlobal(); + + debuggeree.eval( + ` + var dbg = new Debugger(this.debuggee); + var fired = 0; + dbg.memory.onGarbageCollection = _ => { + fired++; + gc(this); + }; + ` + ); + + const dbg = new Debugger(debuggeree); + let fired = 0; + dbg.memory.onGarbageCollection = _ => { + fired++; + }; + + debuggee.eval(`gc(this)`); + + // Let first onGarbageCollection runnable get run. + executeSoon(() => { + + // Let second onGarbageCollection runnable get run. + executeSoon(() => { + + // Even though we request GC'ing a single zone, we can't rely on that + // behavior and both zones could have been scheduled for gc for both + // gc(this) calls. + ok(debuggeree.fired >= 1); + ok(fired >= 1); + + debuggeree.dbg.enabled = dbg.enabled = false; + do_test_finished(); + }); + }); +} diff --git a/js/xpconnect/tests/unit/test_onGarbageCollection-05.js b/js/xpconnect/tests/unit/test_onGarbageCollection-05.js new file mode 100644 index 000000000..a34859ceb --- /dev/null +++ b/js/xpconnect/tests/unit/test_onGarbageCollection-05.js @@ -0,0 +1,37 @@ +// Test that the onGarbageCollection hook reports its gc cycle's number (aka the +// major GC number) and that it is monotonically increasing. + +const root = newGlobal(); +const dbg = new Debugger(); +const wrappedRoot = dbg.addDebuggee(root) + +function run_test() { + do_test_pending(); + + let numFired = 0; + let lastGCCycleNumber = undefined; + + (function loop() { + if (numFired == 10) { + dbg.memory.onGarbageCollection = undefined; + dbg.enabled = false; + return void do_test_finished(); + } + + dbg.memory.onGarbageCollection = data => { + print("onGarbageCollection: " + uneval(data)); + + if (numFired != 0) { + equal(typeof lastGCCycleNumber, "number"); + equal(data.gcCycleNumber - lastGCCycleNumber, 1); + } + + numFired++; + lastGCCycleNumber = data.gcCycleNumber; + + executeSoon(loop); + }; + + root.eval("gc(this)"); + }()); +} diff --git a/js/xpconnect/tests/unit/test_params.js b/js/xpconnect/tests/unit/test_params.js new file mode 100644 index 000000000..dfe4b822a --- /dev/null +++ b/js/xpconnect/tests/unit/test_params.js @@ -0,0 +1,201 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +function run_test() { + + // Load the component manifests. + registerAppManifest(do_get_file('../components/native/chrome.manifest')); + registerAppManifest(do_get_file('../components/js/xpctest.manifest')); + + // Test for each component. + test_component("@mozilla.org/js/xpc/test/native/Params;1"); + test_component("@mozilla.org/js/xpc/test/js/Params;1"); +} + +function test_component(contractid) { + + // Instantiate the object. + var o = Cc[contractid].createInstance(Ci["nsIXPCTestParams"]); + + // Possible comparator functions. + var standardComparator = function(a,b) {return a == b;}; + var dotEqualsComparator = function(a,b) {return a.equals(b); } + var fuzzComparator = function(a,b) {return Math.abs(a - b) < 0.1;}; + var interfaceComparator = function(a,b) {return a.name == b.name; } + var arrayComparator = function(innerComparator) { + return function(a,b) { + if (a.length != b.length) + return false; + for (var i = 0; i < a.length; ++i) + if (!innerComparator(a[i], b[i])) + return false; + return true; + }; + }; + + // Helper test function - takes the name of test method and two values of + // the given type. + // + // The optional comparator argument can be used for alternative notions of + // equality. The comparator should return true on equality. + function doTest(name, val1, val2, comparator) { + if (!comparator) + comparator = standardComparator; + var a = val1; + var b = {value: val2}; + var rv = o[name].call(o, a, b); + do_check_true(comparator(rv, val2)); + do_check_true(comparator(val1, b.value)); + }; + + function doIsTest(name, val1, val1Is, val2, val2Is, valComparator, isComparator) { + if (!isComparator) + isComparator = standardComparator; + var a = val1; + var aIs = val1Is; + var b = {value: val2}; + var bIs = {value: val2Is}; + var rvIs = {}; + var rv = o[name].call(o, aIs, a, bIs, b, rvIs); + do_check_true(valComparator(rv, val2)); + do_check_true(isComparator(rvIs.value, val2Is)); + do_check_true(valComparator(val1, b.value)); + do_check_true(isComparator(val1Is, bIs.value)); + } + + // Special-purpose function for testing arrays of iid_is interfaces, where we + // have 2 distinct sets of dependent parameters. + function doIs2Test(name, val1, val1Size, val1IID, val2, val2Size, val2IID) { + var a = val1; + var aSize = val1Size; + var aIID = val1IID; + var b = {value: val2}; + var bSize = {value: val2Size}; + var bIID = {value: val2IID}; + var rvSize = {}; + var rvIID = {}; + var rv = o[name].call(o, aSize, aIID, a, bSize, bIID, b, rvSize, rvIID); + do_check_true(arrayComparator(interfaceComparator)(rv, val2)); + do_check_true(standardComparator(rvSize.value, val2Size)); + do_check_true(dotEqualsComparator(rvIID.value, val2IID)); + do_check_true(arrayComparator(interfaceComparator)(val1, b.value)); + do_check_true(standardComparator(val1Size, bSize.value)); + do_check_true(dotEqualsComparator(val1IID, bIID.value)); + } + + // Check that the given call (type mismatch) results in an exception being thrown. + function doTypedArrayMismatchTest(name, val1, val1Size, val2, val2Size) { + var comparator = arrayComparator(standardComparator); + var error = false; + try { + doIsTest(name, val1, val1Size, val2, val2Size, comparator); + + // An exception was not thrown as would have been expected. + do_check_true(false); + } + catch (e) { + // An exception was thrown as expected. + do_check_true(true); + } + } + + // Workaround for bug 687612 (inout parameters broken for dipper types). + // We do a simple test of copying a into b, and ignore the rv. + function doTestWorkaround(name, val1) { + var a = val1; + var b = {value: ""}; + o[name].call(o, a, b); + do_check_eq(val1, b.value); + } + + // Test all the different types + doTest("testBoolean", true, false); + doTest("testOctet", 4, 156); + doTest("testShort", -456, 1299); + doTest("testLong", 50060, -12121212); + doTest("testLongLong", 12345, -10000000000); + doTest("testUnsignedShort", 1532, 65000); + doTest("testUnsignedLong", 0, 4000000000); + doTest("testUnsignedLongLong", 215435, 3453492580348535809); + doTest("testFloat", 4.9, -11.2, fuzzComparator); + doTest("testDouble", -80.5, 15000.2, fuzzComparator); + doTest("testChar", "a", "2"); + doTest("testString", "someString", "another string"); + doTest("testWstring", "Why wasnt this", "turned on before? ಠ_ಠ"); + doTest("testWchar", "z", "ア"); + doTestWorkaround("testDOMString", "Beware: ☠ s"); + doTestWorkaround("testAString", "Frosty the ☃ ;-)"); + doTestWorkaround("testAUTF8String", "We deliver 〠!"); + doTestWorkaround("testACString", "Just a regular C string."); + doTest("testJsval", {aprop: 12, bprop: "str"}, 4.22); + + // Test out dipper parameters, since they're special and we can't really test + // inouts. + let outAString = {}; + o.testOutAString(outAString); + do_check_eq(outAString.value, "out"); + try { o.testOutAString(undefined); } catch (e) {} // Don't crash + try { o.testOutAString(null); } catch (e) {} // Don't crash + try { o.testOutAString("string"); } catch (e) {} // Don't crash + + // Helpers to instantiate various test XPCOM objects. + var numAsMade = 0; + function makeA() { + var a = Cc["@mozilla.org/js/xpc/test/js/InterfaceA;1"].createInstance(Ci['nsIXPCTestInterfaceA']); + a.name = 'testA' + numAsMade++; + return a; + }; + var numBsMade = 0; + function makeB() { + var b = Cc["@mozilla.org/js/xpc/test/js/InterfaceB;1"].createInstance(Ci['nsIXPCTestInterfaceB']); + b.name = 'testB' + numBsMade++; + return b; + }; + + // Test arrays. + doIsTest("testShortArray", [2, 4, 6], 3, [1, 3, 5, 7], 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", [-10, -0.5], 2, [1, 3, 1e11, -8e-5 ], 4, arrayComparator(fuzzComparator)); + + doIsTest("testStringArray", ["mary", "hat", "hey", "lid", "tell", "lam"], 6, + ["ids", "fleas", "woes", "wide", "has", "know", "!"], 7, arrayComparator(standardComparator)); + doIsTest("testWstringArray", ["沒有語言", "的偉大嗎?]"], 2, + ["we", "are", "being", "sooo", "international", "right", "now"], 7, arrayComparator(standardComparator)); + doIsTest("testInterfaceArray", [makeA(), makeA()], 2, + [makeA(), makeA(), makeA(), makeA(), makeA(), makeA()], 6, arrayComparator(interfaceComparator)); + + // Test typed arrays and ArrayBuffer aliasing. + var arrayBuffer = new ArrayBuffer(16); + var int16Array = new Int16Array(arrayBuffer, 2, 3); + int16Array.set([-32768, 0, 32767]); + doIsTest("testShortArray", int16Array, 3, new Int16Array([1773, -32768, 32767, 7]), 4, arrayComparator(standardComparator)); + doIsTest("testDoubleArray", new Float64Array([-10, -0.5]), 2, new Float64Array([0, 3.2, 1.0e10, -8.33 ]), 4, arrayComparator(fuzzComparator)); + + // Test sized strings. + var ssTests = ["Tis not possible, I muttered", "give me back my free hardcore!", "quoth the server:", "4〠4"]; + doIsTest("testSizedString", ssTests[0], ssTests[0].length, ssTests[1], ssTests[1].length, standardComparator); + doIsTest("testSizedWstring", ssTests[2], ssTests[2].length, ssTests[3], ssTests[3].length, standardComparator); + + // Test iid_is. + doIsTest("testInterfaceIs", makeA(), Ci['nsIXPCTestInterfaceA'], + makeB(), Ci['nsIXPCTestInterfaceB'], + interfaceComparator, dotEqualsComparator); + + // Test arrays of iids. + doIs2Test("testInterfaceIsArray", [makeA(), makeA(), makeA(), makeA(), makeA()], 5, Ci['nsIXPCTestInterfaceA'], + [makeB(), makeB(), makeB()], 3, Ci['nsIXPCTestInterfaceB']); + + // Test optional array size. + do_check_eq(o.testStringArrayOptionalSize(["some", "string", "array"]), "somestringarray"); + + // Test incorrect (too big) array size parameter; this should throw NOT_ENOUGH_ELEMENTS. + doTypedArrayMismatchTest("testShortArray", new Int16Array([-3, 7, 4]), 4, + new Int16Array([1, -32, 6]), 3); + + // Test type mismatch (int16 <-> uint16); this should throw BAD_CONVERT_JS. + doTypedArrayMismatchTest("testShortArray", new Uint16Array([0, 7, 4, 3]), 4, + new Uint16Array([1, 5, 6]), 3); +} diff --git a/js/xpconnect/tests/unit/test_promise.js b/js/xpconnect/tests/unit/test_promise.js new file mode 100644 index 000000000..46c34a9bc --- /dev/null +++ b/js/xpconnect/tests/unit/test_promise.js @@ -0,0 +1,8 @@ +function run_test() { + var Cu = Components.utils; + sb = new Cu.Sandbox('http://www.example.com'); + sb.do_check_eq = do_check_eq; + Cu.evalInSandbox('do_check_eq(typeof new Promise(function(resolve){resolve();}), "object");', + sb); + do_check_eq(typeof new Promise(function(resolve){resolve();}), "object"); +} diff --git a/js/xpconnect/tests/unit/test_recursive_import.js b/js/xpconnect/tests/unit/test_recursive_import.js new file mode 100644 index 000000000..4145ca49f --- /dev/null +++ b/js/xpconnect/tests/unit/test_recursive_import.js @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope = {}; + Components.utils.import("resource://test/recursive_importA.jsm", scope); + + // A imported correctly + do_check_true(scope.foo() == "foo"); + + // Symbols from B are visible through A + do_check_true(scope.bar.baz() == "baz"); + + // Symbols from A are visible through A, B, A. + do_check_true(scope.bar.qux.foo() == "foo"); +} diff --git a/js/xpconnect/tests/unit/test_reflect_parse.js b/js/xpconnect/tests/unit/test_reflect_parse.js new file mode 100644 index 000000000..48d84d91c --- /dev/null +++ b/js/xpconnect/tests/unit/test_reflect_parse.js @@ -0,0 +1,27 @@ +/* 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/. */ + +/* +({ + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"Program", + body:[ + { + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"ExpressionStatement", + expression:{ + loc:{start:{line:1, column:0}, end:{line:1, column:12}, source:null}, + type:"Literal", + value:"use strict" + } + } + ] +}) +*/ + +function run_test() { + // Reflect.parse is better tested in js shell; this basically tests its presence. + var parseData = Reflect.parse('"use strict"'); + do_check_eq(parseData.body[0].expression.value, "use strict"); +} diff --git a/js/xpconnect/tests/unit/test_resolve_dead_promise.js b/js/xpconnect/tests/unit/test_resolve_dead_promise.js new file mode 100644 index 000000000..818ab5b58 --- /dev/null +++ b/js/xpconnect/tests/unit/test_resolve_dead_promise.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/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1298597 */ + +function run_test() +{ + var sb = Components.utils.Sandbox("http://www.blah.com"); + var resolveFun; + var p1 = new sb.Promise((res, rej) => {resolveFun = res}); + var rejectFun; + var p2 = new sb.Promise((res, rej) => {rejectFun = rej}); + Components.utils.nukeSandbox(sb); + do_check_true(Components.utils.isDeadWrapper(sb), "sb should be dead"); + do_check_true(Components.utils.isDeadWrapper(p1), "p1 should be dead"); + do_check_true(Components.utils.isDeadWrapper(p2), "p2 should be dead"); + + var exception; + + try{ + resolveFun(1); + do_check_true(false); + } catch (e) { + exception = e; + } + do_check_true(exception.toString().includes("can't access dead object"), + "Resolving dead wrapped promise should throw"); + + exception = undefined; + try{ + rejectFun(1); + do_check_true(false); + } catch (e) { + exception = e; + } + do_check_true(exception.toString().includes("can't access dead object"), + "Rejecting dead wrapped promise should throw"); +} diff --git a/js/xpconnect/tests/unit/test_returncode.js b/js/xpconnect/tests/unit/test_returncode.js new file mode 100644 index 000000000..8ba76c976 --- /dev/null +++ b/js/xpconnect/tests/unit/test_returncode.js @@ -0,0 +1,78 @@ +/* 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 {interfaces: Ci, classes: Cc, utils: Cu, manager: Cm, results: Cr} = Components; + +Cu.import("resource:///modules/XPCOMUtils.jsm"); + +function getConsoleMessages() { + let consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService); + let messages = consoleService.getMessageArray().map((m) => m.toString()); + // reset ready for the next call. + consoleService.reset(); + return messages; +} + +function run_test() { + // Load the component manifests. + registerAppManifest(do_get_file('../components/native/chrome.manifest')); + registerAppManifest(do_get_file('../components/js/xpctest.manifest')); + + // and the tests. + test_simple(); + test_nested(); +} + +function test_simple() { + let parent = Cc["@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"] + .createInstance(Ci.nsIXPCTestReturnCodeParent); + let result; + + // flush existing messages before we start testing. + getConsoleMessages(); + + // Ask the C++ to call the JS object which will throw. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_THROW); + Assert.equal(result, Cr.NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS, + "exception caused NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"); + + let messages = getConsoleMessages(); + Assert.equal(messages.length, 1, "got a console message from the exception"); + Assert.ok(messages[0].indexOf("a requested error") != -1, "got the message text"); + + // Ask the C++ to call the JS object which will return success. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_SUCCESS); + Assert.equal(result, Cr.NS_OK, "success is success"); + + Assert.deepEqual(getConsoleMessages(), [], "no messages reported on success."); + + // And finally the point of this test! + // Ask the C++ to call the JS object which will use .returnCode + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_RETURN_RESULTCODE); + Assert.equal(result, Cr.NS_ERROR_FAILURE, + "NS_ERROR_FAILURE was seen as the error code."); + + Assert.deepEqual(getConsoleMessages(), [], "no messages reported with .returnCode"); +} + +function test_nested() { + let parent = Cc["@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"] + .createInstance(Ci.nsIXPCTestReturnCodeParent); + let result; + + // flush existing messages before we start testing. + getConsoleMessages(); + + // Ask the C++ to call the "outer" JS object, which will set .returnCode, but + // then create and call *another* component which itself sets the .returnCode + // to a different value. This checks the returnCode is correctly saved + // across call contexts. + result = parent.callChild(Ci.nsIXPCTestReturnCodeChild.CHILD_SHOULD_NEST_RESULTCODES); + Assert.equal(result, Cr.NS_ERROR_UNEXPECTED, + "NS_ERROR_UNEXPECTED was seen as the error code."); + // We expect one message, which is the child reporting what it got as the + // return code - which should be NS_ERROR_FAILURE + let expected = ["nested child returned " + Cr.NS_ERROR_FAILURE]; + Assert.deepEqual(getConsoleMessages(), expected, "got the correct sub-error"); +} diff --git a/js/xpconnect/tests/unit/test_rtcIdentityProvider.js b/js/xpconnect/tests/unit/test_rtcIdentityProvider.js new file mode 100644 index 000000000..d6918d3b6 --- /dev/null +++ b/js/xpconnect/tests/unit/test_rtcIdentityProvider.js @@ -0,0 +1,35 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let Cu = Components.utils; + let sb = new Cu.Sandbox('https://www.example.com', + { wantGlobalProperties: ['rtcIdentityProvider'] }); + + function exerciseInterface() { + equal(typeof rtcIdentityProvider, 'object'); + equal(typeof rtcIdentityProvider.register, 'function'); + rtcIdentityProvider.register({ + generateAssertion: function(a, b, c) { + return Promise.resolve({ + idp: { domain: 'example.com' }, + assertion: JSON.stringify([a, b, c]) + }); + }, + validateAssertion: function(d, e) { + return Promise.resolve({ + identity: 'user@example.com', + contents: JSON.stringify([d, e]) + }); + } + }); + } + + sb.equal = equal; + Cu.evalInSandbox('(' + exerciseInterface.toSource() + ')();', sb); + ok(sb.rtcIdentityProvider.hasIdp); + + Cu.importGlobalProperties(['rtcIdentityProvider']); + exerciseInterface(); + ok(rtcIdentityProvider.hasIdp); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_atob.js b/js/xpconnect/tests/unit/test_sandbox_atob.js new file mode 100644 index 000000000..0c7586fe2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_atob.js @@ -0,0 +1,10 @@ +function run_test() { + var Cu = Components.utils; + sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["atob", "btoa"] }); + sb.do_check_eq = do_check_eq; + Cu.evalInSandbox('var dummy = "Dummy test.";' + + 'do_check_eq(dummy, atob(btoa(dummy)));' + + 'do_check_eq(btoa("budapest"), "YnVkYXBlc3Q=");', + sb); +} diff --git a/js/xpconnect/tests/unit/test_sandbox_metadata.js b/js/xpconnect/tests/unit/test_sandbox_metadata.js new file mode 100644 index 000000000..c3db2c1b2 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_metadata.js @@ -0,0 +1,58 @@ +/* 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/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=898559 */ + +function run_test() +{ + let sandbox = Components.utils.Sandbox("http://www.blah.com", { + metadata: "test metadata", + addonId: "12345" + }); + + do_check_eq(Components.utils.getSandboxMetadata(sandbox), "test metadata"); + do_check_eq(Components.utils.getSandboxAddonId(sandbox), "12345"); + + sandbox = Components.utils.Sandbox("http://www.blah.com", { + metadata: { foopy: { bar: 2 }, baz: "hi" } + }); + + let metadata = Components.utils.getSandboxMetadata(sandbox); + do_check_eq(metadata.baz, "hi"); + do_check_eq(metadata.foopy.bar, 2); + metadata.baz = "foo"; + + metadata = Components.utils.getSandboxMetadata(sandbox); + do_check_eq(metadata.baz, "foo"); + + metadata = { foo: "bar" }; + Components.utils.setSandboxMetadata(sandbox, metadata); + metadata.foo = "baz"; + metadata = Components.utils.getSandboxMetadata(sandbox); + do_check_eq(metadata.foo, "bar"); + + let thrown = false; + let reflector = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"] + .createInstance(Components.interfaces.nsIXMLHttpRequest); + + try { + Components.utils.setSandboxMetadata(sandbox, { foo: reflector }); + } catch(e) { + thrown = true; + } + + do_check_eq(thrown, true); + + sandbox = Components.utils.Sandbox(this, { + metadata: { foopy: { bar: 2 }, baz: "hi" } + }); + + let inner = Components.utils.evalInSandbox("Components.utils.Sandbox('http://www.blah.com')", sandbox); + + metadata = Components.utils.getSandboxMetadata(inner); + do_check_eq(metadata.baz, "hi"); + do_check_eq(metadata.foopy.bar, 2); + metadata.baz = "foo"; +} + diff --git a/js/xpconnect/tests/unit/test_sandbox_name.js b/js/xpconnect/tests/unit/test_sandbox_name.js new file mode 100644 index 000000000..44e6bbcd4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_sandbox_name.js @@ -0,0 +1,28 @@ +"use strict"; + +const { utils: Cu, interfaces: Ci, classes: Cc } = Components; + +/** + * Test that the name of a sandbox contains the name of all principals. + */ +function test_sandbox_name() { + let names = [ + "http://example.com/?" + Math.random(), + "http://example.org/?" + Math.random() + ]; + let sandbox = Cu.Sandbox(names); + let fileName = Cu.evalInSandbox( + "(new Error()).fileName", + sandbox, + "latest" /*js version*/, + ""/*file name*/ + ); + + for (let name of names) { + Assert.ok(fileName.indexOf(name) != -1, `Name ${name} appears in ${fileName}`); + } +}; + +function run_test() { + test_sandbox_name(); +} diff --git a/js/xpconnect/tests/unit/test_tearoffs.js b/js/xpconnect/tests/unit/test_tearoffs.js new file mode 100644 index 000000000..02f0a6329 --- /dev/null +++ b/js/xpconnect/tests/unit/test_tearoffs.js @@ -0,0 +1,108 @@ +/* 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 Cc = Components.classes; +const Ci = Components.interfaces; + +function run_test() { + + // Load the component manifest containing our test interface implementations. + Components.manager.autoRegister(do_get_file('../components/js/xpctest.manifest')); + + // Shortcut the interfaces we're using. + var ifs = { + a: Ci['nsIXPCTestInterfaceA'], + b: Ci['nsIXPCTestInterfaceB'], + c: Ci['nsIXPCTestInterfaceC'] + }; + + // Shortcut the class we're instantiating. This implements all three interfaces. + var cls = Cc["@mozilla.org/js/xpc/test/js/TestInterfaceAll;1"]; + + // Run through the logic a few times. + for (i = 0; i < 2; ++i) + play_with_tearoffs(ifs, cls); +} + +function play_with_tearoffs(ifs, cls) { + + // Allocate a bunch of objects, QI-ed to B. + var instances = []; + for (var i = 0; i < 300; ++i) + instances.push(cls.createInstance(ifs.b)); + + // Nothing to collect. + gc(); + + // QI them to A. + instances.forEach(function(v, i, a) { v.QueryInterface(ifs.a); }); + + // QI them to C. + instances.forEach(function(v, i, a) { v.QueryInterface(ifs.c); }); + + // Check + do_check_true('name' in instances[10], 'Have the prop from A/B'); + do_check_true('someInteger' in instances[10], 'Have the prop from C'); + + // Grab tearoff reflections for a and b. + var aTearOffs = instances.map(function(v, i, a) { return v.nsIXPCTestInterfaceA; } ); + var bTearOffs = instances.map(function(v, i, a) { return v.nsIXPCTestInterfaceB; } ); + + // Check + do_check_true('name' in aTearOffs[1], 'Have the prop from A'); + do_check_true(!('someInteger' in aTearOffs[1]), 'Dont have the prop from C'); + + // Nothing to collect. + gc(); + + // Null out every even instance pointer. + for (var i = 0; i < instances.length; ++i) + if (i % 2 == 0) + instances[i] = null; + + // Nothing to collect, since we still have the A and B tearoff reflections. + gc(); + + // Null out A tearoff reflections that are a multiple of 3. + for (var i = 0; i < aTearOffs.length; ++i) + if (i % 3 == 0) + aTearOffs[i] = null; + + // Nothing to collect, since we still have the B tearoff reflections. + gc(); + + // Null out B tearoff reflections that are a multiple of 5. + for (var i = 0; i < bTearOffs.length; ++i) + if (i % 5 == 0) + bTearOffs[i] = null; + + // This should collect every 30th object (indices that are multiples of 2, 3, and 5). + gc(); + + // Kill the b tearoffs entirely. + bTearOffs = 0; + + // Collect more. + gc(); + + // Get C tearoffs. + var cTearOffs = instances.map(function(v, i, a) { return v ? v.nsIXPCTestInterfaceC : null; } ); + + // Check. + do_check_true(!('name' in cTearOffs[1]), 'Dont have the prop from A'); + do_check_true('someInteger' in cTearOffs[1], 'have the prop from C'); + + // Null out the a tearoffs. + aTearOffs = null; + + // Collect all even indices. + gc(); + + // Collect all indices. + instances = null; + gc(); + + // Give ourselves a pat on the back. :-) + do_check_true(true, "Got all the way through without crashing!"); +} diff --git a/js/xpconnect/tests/unit/test_textDecoder.js b/js/xpconnect/tests/unit/test_textDecoder.js new file mode 100644 index 000000000..0b31750a4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_textDecoder.js @@ -0,0 +1,12 @@ +function run_test() { + var Cu = Components.utils; + sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["TextDecoder", "TextEncoder"] }); + sb.do_check_eq = do_check_eq; + Cu.evalInSandbox('do_check_eq(new TextDecoder().encoding, "utf-8");' + + 'do_check_eq(new TextEncoder().encoding, "utf-8");', + sb); + Cu.importGlobalProperties(["TextDecoder", "TextEncoder"]); + do_check_eq(new TextDecoder().encoding, "utf-8"); + do_check_eq(new TextEncoder().encoding, "utf-8"); +} diff --git a/js/xpconnect/tests/unit/test_unload.js b/js/xpconnect/tests/unit/test_unload.js new file mode 100644 index 000000000..e3ca8ce88 --- /dev/null +++ b/js/xpconnect/tests/unit/test_unload.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function run_test() { + var scope1 = {}; + var global1 = Components.utils.import("resource://gre/modules/NetUtil.jsm", scope1); + + var scope2 = {}; + var global2 = Components.utils.import("resource://gre/modules/NetUtil.jsm", scope2); + + do_check_true(global1 === global2); + do_check_true(scope1.NetUtil === scope2.NetUtil); + + Components.utils.unload("resource://gre/modules/NetUtil.jsm"); + + var scope3 = {}; + var global3 = Components.utils.import("resource://gre/modules/NetUtil.jsm", scope3); + + do_check_false(global1 === global3); + do_check_false(scope1.NetUtil === scope3.NetUtil); + + // When the jsm was unloaded, the value of all its global's properties were + // set to undefined. While it must be safe (not crash) to call into the + // module, we expect it to throw an error (e.g., when trying to use Ci). + try { scope1.NetUtil.newURI("http://www.example.com"); } catch (e) {} + try { scope3.NetUtil.newURI("http://www.example.com"); } catch (e) {} +} diff --git a/js/xpconnect/tests/unit/test_url.js b/js/xpconnect/tests/unit/test_url.js new file mode 100644 index 000000000..da2502800 --- /dev/null +++ b/js/xpconnect/tests/unit/test_url.js @@ -0,0 +1,10 @@ +function run_test() { + var Cu = Components.utils; + var sb = new Cu.Sandbox('http://www.example.com', + { wantGlobalProperties: ["URL"] }); + sb.do_check_eq = do_check_eq; + Cu.evalInSandbox('do_check_eq(new URL("http://www.example.com").host, "www.example.com");', + sb); + Cu.importGlobalProperties(["URL"]); + do_check_eq(new URL("http://www.example.com").host, "www.example.com"); +} diff --git a/js/xpconnect/tests/unit/test_want_components.js b/js/xpconnect/tests/unit/test_want_components.js new file mode 100644 index 000000000..5ccebcf9c --- /dev/null +++ b/js/xpconnect/tests/unit/test_want_components.js @@ -0,0 +1,8 @@ +function run_test() { + var cu = Components.utils; + var sb = cu.Sandbox(this, + {wantComponents: false}); + + var rv = cu.evalInSandbox("this.Components", sb); + do_check_eq(rv, undefined); +} diff --git a/js/xpconnect/tests/unit/test_watchdog_default.js b/js/xpconnect/tests/unit/test_watchdog_default.js new file mode 100644 index 000000000..d5dac7b6a --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_default.js @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + // Check that we properly implement whatever behavior is specified by the + // default profile for this configuration. + checkWatchdog(isWatchdogEnabled(), continueTest); + yield; + do_test_finished(); + yield; +} diff --git a/js/xpconnect/tests/unit/test_watchdog_disable.js b/js/xpconnect/tests/unit/test_watchdog_disable.js new file mode 100644 index 000000000..dcdf21ce4 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_disable.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + setWatchdogEnabled(false); + checkWatchdog(false, continueTest); + yield; + do_test_finished(); + yield; +} diff --git a/js/xpconnect/tests/unit/test_watchdog_enable.js b/js/xpconnect/tests/unit/test_watchdog_enable.js new file mode 100644 index 000000000..8e31e1316 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_enable.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + setWatchdogEnabled(true); + checkWatchdog(true, continueTest); + yield; + do_test_finished(); + yield; +} diff --git a/js/xpconnect/tests/unit/test_watchdog_hibernate.js b/js/xpconnect/tests/unit/test_watchdog_hibernate.js new file mode 100644 index 000000000..ce3642963 --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_hibernate.js @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + + setWatchdogEnabled(true); + + // It's unlikely that we've ever hibernated at this point, but the timestamps + // default to 0, so this should always be true. + var now = Date.now() * 1000; + var startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); + var stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop"); + do_log_info("Pre-hibernation statistics:"); + do_log_info("now: " + now / 1000000); + do_log_info("startHibernation: " + startHibernation / 1000000); + do_log_info("stopHibernation: " + stopHibernation / 1000000); + do_check_true(startHibernation < now); + do_check_true(stopHibernation < now); + + // When the watchdog runs, it hibernates if there's been no activity for the + // last 2 seconds, otherwise it sleeps for 1 second. As such, given perfect + // scheduling, we should never have more than 3 seconds of inactivity without + // hibernating. To add some padding for automation, we mandate that hibernation + // must begin between 2 and 5 seconds from now. + var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback(continueTest, 10000, Ci.nsITimer.TYPE_ONE_SHOT); + simulateActivityCallback(false); + yield; + + simulateActivityCallback(true); + busyWait(1000); // Give the watchdog time to wake up on the condvar. + var stateChange = Cu.getWatchdogTimestamp("ContextStateChange"); + startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); + stopHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStop"); + do_log_info("Post-hibernation statistics:"); + do_log_info("stateChange: " + stateChange / 1000000); + do_log_info("startHibernation: " + startHibernation / 1000000); + do_log_info("stopHibernation: " + stopHibernation / 1000000); + // XPCOM timers, JS times, and PR_Now() are apparently not directly + // comparable, as evidenced by certain seemingly-impossible timing values + // that occasionally get logged in windows automation. We're really just + // making sure this behavior is roughly as expected on the macro scale, + // so we add a 1 second fuzz factor here. + const FUZZ_FACTOR = 1 * 1000 * 1000; + do_check_true(stateChange > now + 10*1000*1000 - FUZZ_FACTOR); + do_check_true(startHibernation > now + 2*1000*1000 - FUZZ_FACTOR); + do_check_true(startHibernation < now + 5*1000*1000 + FUZZ_FACTOR); + do_check_true(stopHibernation > now + 10*1000*1000 - FUZZ_FACTOR); + + do_test_finished(); + yield; +} diff --git a/js/xpconnect/tests/unit/test_watchdog_toggle.js b/js/xpconnect/tests/unit/test_watchdog_toggle.js new file mode 100644 index 000000000..f0d14c2df --- /dev/null +++ b/js/xpconnect/tests/unit/test_watchdog_toggle.js @@ -0,0 +1,13 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +function testBody() { + var defaultBehavior = isWatchdogEnabled(); + setWatchdogEnabled(!defaultBehavior); + setWatchdogEnabled(defaultBehavior); + checkWatchdog(defaultBehavior, continueTest); + yield; + do_test_finished(); + yield; +} diff --git a/js/xpconnect/tests/unit/test_weak_keys.js b/js/xpconnect/tests/unit/test_weak_keys.js new file mode 100644 index 000000000..e70983e9c --- /dev/null +++ b/js/xpconnect/tests/unit/test_weak_keys.js @@ -0,0 +1,45 @@ +/* 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/. */ + +/* See https://bugzilla.mozilla.org/show_bug.cgi?id=1165807 */ + +function run_test() +{ + var bunnies = new String("bunnies"); + var lizards = new String("lizards"); + + var weakset = new WeakSet([bunnies, lizards]); + var weakmap = new WeakMap(); + weakmap.set(bunnies, 23); + weakmap.set(lizards, "oh no"); + + var keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(bunnies); + equal(keys, undefined, "test nondeterministicGetWeakMapKeys on non-WeakMap"); + + keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 2, "length of nondeterministicGetWeakMapKeys"); + equal(weakmap.get(bunnies), 23, "check bunnies in weakmap"); + equal(weakmap.get(lizards), "oh no", "check lizards in weakmap"); + + keys = ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(bunnies); + equal(keys, undefined, "test nondeterministicGetWeakSetKeys on non-WeakMap"); + + keys = ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 2, "length of nondeterministicGetWeakSetKeys"); + ok(weakset.has(bunnies), "check bunnies in weakset"); + ok(weakset.has(lizards), "check lizards in weakset"); + + bunnies = null; + keys = null; + + Components.utils.forceGC(); + + keys = ThreadSafeChromeUtils.nondeterministicGetWeakMapKeys(weakmap); + equal(keys.length, 1, "length of nondeterministicGetWeakMapKeys after GC"); + equal(weakmap.get(lizards), "oh no", "check lizards still in weakmap"); + + keys = ThreadSafeChromeUtils.nondeterministicGetWeakSetKeys(weakset); + equal(keys.length, 1, "length of nondeterministicGetWeakSetKeys after GC"); + ok(weakset.has(lizards), "check lizards still in weakset"); +} diff --git a/js/xpconnect/tests/unit/test_writeToGlobalPrototype.js b/js/xpconnect/tests/unit/test_writeToGlobalPrototype.js new file mode 100644 index 000000000..0062861eb --- /dev/null +++ b/js/xpconnect/tests/unit/test_writeToGlobalPrototype.js @@ -0,0 +1,76 @@ +var G = 3; + +const Cu = Components.utils; + +function run_test() +{ + let s = Cu.Sandbox(this, {sandboxPrototype: this, writeToGlobalPrototype: true}); + + Cu.evalInSandbox("a = 3", s); + Cu.evalInSandbox("var b = 3", s); + Cu.evalInSandbox("const c = 3", s); + Cu.evalInSandbox("this.d = 3", s); + Cu.evalInSandbox("function e() { return 3; }", s); + + do_check_eq(Cu.evalInSandbox("a", s), 3); + do_check_eq(Cu.evalInSandbox("b", s), 3); + do_check_eq(Cu.evalInSandbox("c", s), 3); + do_check_eq(Cu.evalInSandbox("d", s), 3); + do_check_eq(Cu.evalInSandbox("e()", s), 3); + + do_check_eq(a, 3); + do_check_eq(b, 3); + // c is a lexical binding and does not write to the global prototype + do_check_eq(d, 3); + do_check_eq(e(), 3); + + a = 12; + do_check_eq(Cu.evalInSandbox("a", s), 12); + b = 12; + do_check_eq(Cu.evalInSandbox("b", s), 12); + d = 12; + do_check_eq(Cu.evalInSandbox("d", s), 12); + + this.q = 3; + do_check_eq(Cu.evalInSandbox("q", s), 3); + Cu.evalInSandbox("q = 12", s); + do_check_eq(q, 12); + + do_check_eq(Cu.evalInSandbox("G", s), 3); + Cu.evalInSandbox("G = 12", s); + do_check_eq(G, 12); + + Cu.evalInSandbox("Object.defineProperty(this, 'x', {enumerable: false, value: 3})", s); + do_check_eq(Cu.evalInSandbox("x", s), 3); + do_check_eq(x, 3); + for (var p in this) { + do_check_neq(p, "x"); + } + + Cu.evalInSandbox("Object.defineProperty(this, 'y', {get: function() { this.gotten = true; return 3; }})", s); + do_check_eq(y, 3); + do_check_eq(Cu.evalInSandbox("gotten", s), true); + do_check_eq(gotten, true); + + Cu.evalInSandbox("this.gotten = false", s); + do_check_eq(Cu.evalInSandbox("y", s), 3); + do_check_eq(Cu.evalInSandbox("gotten", s), true); + do_check_eq(gotten, true); + + Cu.evalInSandbox("Object.defineProperty(this, 'z', {get: function() { this.gotten = true; return 3; }, set: function(v) { this.setTo = v; }})", s); + z = 12; + do_check_eq(setTo, 12); + do_check_eq(z, 3); + do_check_eq(gotten, true); + do_check_eq(Cu.evalInSandbox("gotten", s), true); + gotten = false; + do_check_eq(Cu.evalInSandbox("gotten", s), false); + + Cu.evalInSandbox("z = 20", s); + do_check_eq(setTo, 20); + do_check_eq(Cu.evalInSandbox("z", s), 3); + do_check_eq(gotten, true); + do_check_eq(Cu.evalInSandbox("gotten", s), true); + gotten = false; + do_check_eq(Cu.evalInSandbox("gotten", s), false); +} diff --git a/js/xpconnect/tests/unit/test_xpcomutils.js b/js/xpconnect/tests/unit/test_xpcomutils.js new file mode 100644 index 000000000..f43454253 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcomutils.js @@ -0,0 +1,246 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- + * vim: sw=4 ts=4 sts=4 et + * 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/. */ + +/** + * This file tests the methods on XPCOMUtils.jsm. + */ + +Components.utils.import("resource://gre/modules/Preferences.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +const Cc = Components.classes; +const Ci = Components.interfaces; + + +//////////////////////////////////////////////////////////////////////////////// +//// Tests + +add_test(function test_generateQI_string_names() +{ + var x = { + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIClassInfo, + "nsIDOMNode" + ]) + }; + + try { + x.QueryInterface(Components.interfaces.nsIClassInfo); + } catch(e) { + do_throw("Should QI to nsIClassInfo"); + } + try { + x.QueryInterface(Components.interfaces.nsIDOMNode); + } catch(e) { + do_throw("Should QI to nsIDOMNode"); + } + try { + x.QueryInterface(Components.interfaces.nsIDOMDocument); + do_throw("QI should not have succeeded!"); + } catch(e) {} + run_next_test(); +}); + + +add_test(function test_generateCI() +{ + const classID = Components.ID("562dae2e-7cff-432b-995b-3d4c03fa2b89"); + const classDescription = "generateCI test component"; + const flags = Components.interfaces.nsIClassInfo.DOM_OBJECT; + var x = { + QueryInterface: XPCOMUtils.generateQI([]), + classInfo: XPCOMUtils.generateCI({classID: classID, + interfaces: [], + flags: flags, + classDescription: classDescription}) + }; + + try { + var ci = x.QueryInterface(Components.interfaces.nsIClassInfo); + ci = ci.QueryInterface(Components.interfaces.nsISupports); + ci = ci.QueryInterface(Components.interfaces.nsIClassInfo); + do_check_eq(ci.classID, classID); + do_check_eq(ci.flags, flags); + do_check_eq(ci.classDescription, classDescription); + } catch(e) { + do_throw("Classinfo for x should not be missing or broken"); + } + run_next_test(); +}); + +add_test(function test_defineLazyGetter() +{ + let accessCount = 0; + let obj = { + inScope: false + }; + const TEST_VALUE = "test value"; + XPCOMUtils.defineLazyGetter(obj, "foo", function() { + accessCount++; + this.inScope = true; + return TEST_VALUE; + }); + do_check_eq(accessCount, 0); + + // Get the property, making sure the access count has increased. + do_check_eq(obj.foo, TEST_VALUE); + do_check_eq(accessCount, 1); + do_check_true(obj.inScope); + + // Get the property once more, making sure the access count has not + // increased. + do_check_eq(obj.foo, TEST_VALUE); + do_check_eq(accessCount, 1); + run_next_test(); +}); + + +add_test(function test_defineLazyServiceGetter() +{ + let obj = { }; + XPCOMUtils.defineLazyServiceGetter(obj, "service", + "@mozilla.org/consoleservice;1", + "nsIConsoleService"); + let service = Cc["@mozilla.org/consoleservice;1"]. + getService(Ci.nsIConsoleService); + + // Check that the lazy service getter and the actual service have the same + // properties. + for (let prop in obj.service) + do_check_true(prop in service); + for (let prop in service) + do_check_true(prop in obj.service); + run_next_test(); +}); + + +add_test(function test_defineLazyPreferenceGetter() +{ + const PREF = "xpcomutils.test.pref"; + + let obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "defaultValue"); + + + equal(obj.pref, "defaultValue", "Should return the default value before pref is set"); + + Preferences.set(PREF, "currentValue"); + + + do_print("Create second getter on new object"); + + obj = {}; + XPCOMUtils.defineLazyPreferenceGetter(obj, "pref", PREF, "defaultValue"); + + + equal(obj.pref, "currentValue", "Should return the current value on initial read when pref is already set"); + + Preferences.set(PREF, "newValue"); + + equal(obj.pref, "newValue", "Should return new value after preference change"); + + Preferences.set(PREF, "currentValue"); + + equal(obj.pref, "currentValue", "Should return new value after second preference change"); + + + Preferences.reset(PREF); + + equal(obj.pref, "defaultValue", "Should return default value after pref is reset"); + + run_next_test(); +}); + + +add_test(function test_categoryRegistration() +{ + const CATEGORY_NAME = "test-cat"; + const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1"; + const XULAPPINFO_CID = Components.ID("{fc937916-656b-4fb3-a395-8c63569e27a8}"); + + // Create a fake app entry for our category registration apps filter. + let tmp = {}; + Components.utils.import("resource://testing-common/AppInfo.jsm", tmp); + let XULAppInfo = tmp.newAppInfo({ + name: "catRegTest", + ID: "{adb42a9a-0d19-4849-bf4d-627614ca19be}", + version: "1", + platformVersion: "", + }); + let XULAppInfoFactory = { + createInstance: function (outer, iid) { + if (outer != null) + throw Cr.NS_ERROR_NO_AGGREGATION; + return XULAppInfo.QueryInterface(iid); + } + }; + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + XULAPPINFO_CID, + "XULAppInfo", + XULAPPINFO_CONTRACTID, + XULAppInfoFactory + ); + + // Load test components. + do_load_manifest("CatRegistrationComponents.manifest"); + + const EXPECTED_ENTRIES = new Map([ + ["CatRegisteredComponent", "@unit.test.com/cat-registered-component;1"], + ["CatAppRegisteredComponent", "@unit.test.com/cat-app-registered-component;1"], + ]); + + // Verify the correct entries are registered in the "test-cat" category. + for (let [name, value] of XPCOMUtils.enumerateCategoryEntries(CATEGORY_NAME)) { + print("Verify that the name/value pair exists in the expected entries."); + ok(EXPECTED_ENTRIES.has(name)); + do_check_eq(EXPECTED_ENTRIES.get(name), value); + EXPECTED_ENTRIES.delete(name); + } + print("Check that all of the expected entries have been deleted."); + do_check_eq(EXPECTED_ENTRIES.size, 0); + run_next_test(); +}); + +add_test(function test_generateSingletonFactory() +{ + const XPCCOMPONENT_CONTRACTID = "@mozilla.org/singletonComponentTest;1"; + const XPCCOMPONENT_CID = Components.ID("{31031c36-5e29-4dd9-9045-333a5d719a3e}"); + + function XPCComponent() {} + XPCComponent.prototype = { + classID: XPCCOMPONENT_CID, + _xpcom_factory: XPCOMUtils.generateSingletonFactory(XPCComponent), + QueryInterface: XPCOMUtils.generateQI([]) + }; + let NSGetFactory = XPCOMUtils.generateNSGetFactory([XPCComponent]); + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + registrar.registerFactory( + XPCCOMPONENT_CID, + "XPCComponent", + XPCCOMPONENT_CONTRACTID, + NSGetFactory(XPCCOMPONENT_CID) + ); + + // First, try to instance the component. + let instance = Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports); + // Try again, check that it returns the same instance as before. + do_check_eq(instance, + Cc[XPCCOMPONENT_CONTRACTID].createInstance(Ci.nsISupports)); + // Now, for sanity, check that getService is also returning the same instance. + do_check_eq(instance, + Cc[XPCCOMPONENT_CONTRACTID].getService(Ci.nsISupports)); + + run_next_test(); +}); + +//////////////////////////////////////////////////////////////////////////////// +//// Test Runner + +function run_test() +{ + run_next_test(); +} diff --git a/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js new file mode 100644 index 000000000..bf7b65927 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xpcwn_tamperproof.js @@ -0,0 +1,171 @@ +// Test that it's not possible to create expando properties on XPCWNs. +// See . + +const Cc = Components.classes; +const Ci = Components.interfaces; + +function check_throws(f) { + try { + f(); + } catch (exc) { + return; + } + throw new TypeError("Expected exception, no exception thrown"); +} + +/* + * Test that XPCWrappedNative objects do not permit expando properties to be created. + * + * This function is called twice. The first time, realObj is an nsITimer XPCWN + * and accessObj === realObj. + * + * The second time, accessObj is a scripted proxy with realObj as its target. + * So the second time we are testing that scripted proxies don't magically + * bypass whatever mechanism enforces the expando policy on XPCWNs. + */ +function test_tamperproof(realObj, accessObj, {method, constant, attribute}) { + // Assignment can't create an expando property. + check_throws(function () { accessObj.expando = 14; }); + do_check_false("expando" in realObj); + + // Strict assignment throws. + check_throws(function () { "use strict"; accessObj.expando = 14; }); + do_check_false("expando" in realObj); + + // Assignment to an inherited method name doesn't work either. + check_throws(function () { accessObj.hasOwnProperty = () => "lies"; }); + check_throws(function () { "use strict"; accessObj.hasOwnProperty = () => "lies"; }); + do_check_false(realObj.hasOwnProperty("hasOwnProperty")); + + // Assignment to a method name doesn't work either. + let originalMethod; + if (method) { + originalMethod = accessObj[method]; + accessObj[method] = "nope"; // non-writable data property, no exception in non-strict code + check_throws(function () { "use strict"; accessObj[method] = "nope"; }); + do_check_true(realObj[method] === originalMethod); + } + + // A constant is the same thing. + let originalConstantValue; + if (constant) { + originalConstantValue = accessObj[constant]; + accessObj[constant] = "nope"; + do_check_eq(realObj[constant], originalConstantValue); + check_throws(function () { "use strict"; accessObj[constant] = "nope"; }); + do_check_eq(realObj[constant], originalConstantValue); + } + + // Assignment to a readonly accessor property with no setter doesn't work either. + let originalAttributeDesc; + if (attribute) { + originalAttributeDesc = Object.getOwnPropertyDescriptor(realObj, attribute); + do_check_true("set" in originalAttributeDesc); + do_check_true(originalAttributeDesc.set === undefined); + + accessObj[attribute] = "nope"; // accessor property with no setter: no exception in non-strict code + check_throws(function () { "use strict"; accessObj[attribute] = "nope"; }); + + let desc = Object.getOwnPropertyDescriptor(realObj, attribute); + do_check_true("set" in desc); + do_check_eq(originalAttributeDesc.get, desc.get); + do_check_eq(undefined, desc.set); + } + + // Reflect.set doesn't work either. + if (method) { + do_check_false(Reflect.set({}, method, "bad", accessObj)); + do_check_eq(realObj[method], originalMethod); + } + if (attribute) { + do_check_false(Reflect.set({}, attribute, "bad", accessObj)); + do_check_eq(originalAttributeDesc.get, Object.getOwnPropertyDescriptor(realObj, attribute).get); + } + + // Object.defineProperty can't do anything either. + let names = ["expando"]; + if (method) names.push(method); + if (constant) names.push(constant); + if (attribute) names.push(attribute); + for (let name of names) { + let originalDesc = Object.getOwnPropertyDescriptor(realObj, name); + check_throws(function () { + Object.defineProperty(accessObj, name, {configurable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {writable: true}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {get: function () { return "lies"; }}); + }); + check_throws(function () { + Object.defineProperty(accessObj, name, {value: "bad"}); + }); + let desc = Object.getOwnPropertyDescriptor(realObj, name); + if (originalDesc === undefined) { + do_check_eq(undefined, desc); + } else { + do_check_eq(originalDesc.configurable, desc.configurable); + do_check_eq(originalDesc.enumerable, desc.enumerable); + do_check_eq(originalDesc.writable, desc.writable); + do_check_eq(originalDesc.value, desc.value); + do_check_eq(originalDesc.get, desc.get); + do_check_eq(originalDesc.set, desc.set); + } + } + + // Existing properties can't be deleted. + if (method) { + do_check_false(delete accessObj[method]); + check_throws(function () { "use strict"; delete accessObj[method]; }); + do_check_eq(realObj[method], originalMethod); + } + if (constant) { + do_check_false(delete accessObj[constant]); + check_throws(function () { "use strict"; delete accessObj[constant]; }); + do_check_eq(realObj[constant], originalConstantValue); + } + if (attribute) { + do_check_false(delete accessObj[attribute]); + check_throws(function () { "use strict"; delete accessObj[attribute]; }); + desc = Object.getOwnPropertyDescriptor(realObj, attribute); + do_check_eq(originalAttributeDesc.get, desc.get); + } +} + +function test_twice(obj, options) { + test_tamperproof(obj, obj, options); + + let handler = { + getPrototypeOf(t) { + return new Proxy(Object.getPrototypeOf(t), handler); + } + }; + test_tamperproof(obj, new Proxy(obj, handler), options); +} + +function run_test() { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + test_twice(timer, { + method: "init", + constant: "TYPE_ONE_SHOT", + attribute: "callback" + }); + + let principal = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal); + test_twice(principal, {}); + + test_twice(Object.getPrototypeOf(principal), { + method: "subsumes", + constant: "APP_STATUS_INSTALLED", + attribute: "origin" + }); + + // Test a tearoff object. + Components.manager.autoRegister(do_get_file('../components/js/xpctest.manifest')); + let b = Cc["@mozilla.org/js/xpc/test/js/TestInterfaceAll;1"].createInstance(Ci.nsIXPCTestInterfaceB); + let tearoff = b.nsIXPCTestInterfaceA; + test_twice(tearoff, { + method: "QueryInterface" + }); +} diff --git a/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js b/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js new file mode 100644 index 000000000..5c3397670 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_SavedFrame-02.js @@ -0,0 +1,75 @@ +// Test calling SavedFrame getters across wrappers from privileged and +// un-privileged globals. + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +const lowP = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal); +const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +const low = new Cu.Sandbox(lowP); +const high = new Cu.Sandbox(highP); + +function run_test() { + // Privileged compartment accessing unprivileged stack. + high.stack = getSavedFrameInstanceFromSandbox(low); + Cu.evalInSandbox("this.parent = stack.parent", high); + Cu.evalInSandbox("this.asyncParent = stack.asyncParent", high); + Cu.evalInSandbox("this.source = stack.source", high); + Cu.evalInSandbox("this.functionDisplayName = stack.functionDisplayName", high); + + // Un-privileged compartment accessing privileged stack. + low.stack = getSavedFrameInstanceFromSandbox(high); + try { + Cu.evalInSandbox("this.parent = stack.parent", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.asyncParent = stack.asyncParent", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.source = stack.source", low); + } catch (e) { } + try { + Cu.evalInSandbox("this.functionDisplayName = stack.functionDisplayName", low); + } catch (e) { } + + // Privileged compartment accessing privileged stack. + let stack = getSavedFrameInstanceFromSandbox(high); + let parent = stack.parent; + let asyncParent = stack.asyncParent; + let source = stack.source; + let functionDisplayName = stack.functionDisplayName; + + ok(true, "Didn't crash"); +} + +// Get a SavedFrame instance from inside the given sandbox. +// +// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't +// available to sandboxes that don't have the system principal. The easiest way +// to get the SavedFrame is to use the Debugger API to track allocation sites +// and then do an allocation. +function getSavedFrameInstanceFromSandbox(sandbox) { + const dbg = new Debugger(sandbox); + + dbg.memory.trackingAllocationSites = true; + Cu.evalInSandbox("(function iife() { return new RegExp }())", sandbox); + const allocs = dbg.memory.drainAllocationsLog().filter(e => e.class === "RegExp"); + dbg.memory.trackingAllocationSites = false; + + ok(allocs[0], "We should observe the allocation"); + const { frame } = allocs[0]; + + if (sandbox !== high) { + ok(Cu.isXrayWrapper(frame), "`frame` should be an xray..."); + equal(Object.prototype.toString.call(Cu.waiveXrays(frame)), + "[object SavedFrame]", + "...and that xray should wrap a SavedFrame"); + } + + return frame; +} diff --git a/js/xpconnect/tests/unit/test_xray_SavedFrame.js b/js/xpconnect/tests/unit/test_xray_SavedFrame.js new file mode 100644 index 000000000..6a931cff0 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xray_SavedFrame.js @@ -0,0 +1,108 @@ +// Bug 1117242: Test calling SavedFrame getters from globals that don't subsume +// that frame's principals. + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +Cu.import("resource://gre/modules/jsdebugger.jsm"); +addDebuggerToGlobal(this); + +const lowP = Cc["@mozilla.org/nullprincipal;1"].createInstance(Ci.nsIPrincipal); +const midP = [lowP, "http://other.com"]; +const highP = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal); + +const low = new Cu.Sandbox(lowP); +const mid = new Cu.Sandbox(midP); +const high = new Cu.Sandbox(highP); + +function run_test() { + // Test that the priveleged view of a SavedFrame from a subsumed compartment + // is the same view that the subsumed compartment gets. Create the following + // chain of function calls (with some intermediate system-principaled frames + // due to implementation): + // + // low.lowF -> mid.midF -> high.highF -> high.saveStack + // + // Where high.saveStack gets monkey patched to create stacks in each of our + // sandboxes. + + Cu.evalInSandbox("function highF() { return saveStack(); }", high); + + mid.highF = () => high.highF(); + Cu.evalInSandbox("function midF() { return highF(); }", mid); + + low.midF = () => mid.midF(); + Cu.evalInSandbox("function lowF() { return midF(); }", low); + + const expected = [ + { + sandbox: low, + frames: ["lowF"], + }, + { + sandbox: mid, + frames: ["midF", "lowF"], + }, + { + sandbox: high, + frames: ["getSavedFrameInstanceFromSandbox", + "saveStack", + "highF", + "run_test/mid.highF", + "midF", + "run_test/low.midF", + "lowF", + "run_test", + "_execute_test", + null], + } + ]; + + for (let { sandbox, frames } of expected) { + high.saveStack = function saveStack() { + return getSavedFrameInstanceFromSandbox(sandbox); + }; + + const xrayStack = low.lowF(); + equal(xrayStack.functionDisplayName, "getSavedFrameInstanceFromSandbox", + "Xrays should always be able to see everything."); + + let waived = Cu.waiveXrays(xrayStack); + do { + ok(frames.length, + "There should still be more expected frames while we have actual frames."); + equal(waived.functionDisplayName, frames.shift(), + "The waived wrapper should give us the stack's compartment's view."); + waived = waived.parent; + } while (waived); + } +} + +// Get a SavedFrame instance from inside the given sandbox. +// +// We can't use Cu.getJSTestingFunctions().saveStack() because Cu isn't +// available to sandboxes that don't have the system principal. The easiest way +// to get the SavedFrame is to use the Debugger API to track allocation sites +// and then do an allocation. +function getSavedFrameInstanceFromSandbox(sandbox) { + const dbg = new Debugger(sandbox); + + dbg.memory.trackingAllocationSites = true; + Cu.evalInSandbox("new Object", sandbox); + const allocs = dbg.memory.drainAllocationsLog(); + dbg.memory.trackingAllocationSites = false; + + ok(allocs[0], "We should observe the allocation"); + const { frame } = allocs[0]; + + if (sandbox !== high) { + ok(Cu.isXrayWrapper(frame), "`frame` should be an xray..."); + equal(Object.prototype.toString.call(Cu.waiveXrays(frame)), + "[object SavedFrame]", + "...and that xray should wrap a SavedFrame"); + } + + return frame; +} + diff --git a/js/xpconnect/tests/unit/test_xrayed_iterator.js b/js/xpconnect/tests/unit/test_xrayed_iterator.js new file mode 100644 index 000000000..1f82fd890 --- /dev/null +++ b/js/xpconnect/tests/unit/test_xrayed_iterator.js @@ -0,0 +1,36 @@ +const Cu = Components.utils; +function run_test() { + + var toEval = [ + "var customIterator = {", + " _array: [6, 7, 8, 9],", + " __iterator__: function() {", + " for (var i = 0; i < this._array.length; ++i)", + " yield this._array[i];", + " }", + "}" + ].join('\n'); + + function checkIterator(iterator) { + var control = [6, 7, 8, 9]; + var i = 0; + for (var item in iterator) { + do_check_eq(item, control[i]); + ++i; + } + } + + // First, try in our own scope. + eval(toEval); + checkIterator(customIterator); + + // Next, try a vanilla CCW. + var sbChrome = Cu.Sandbox(this); + Cu.evalInSandbox(toEval, sbChrome, '1.7'); + checkIterator(sbChrome.customIterator); + + // Finally, try an Xray waiver. + var sbContent = Cu.Sandbox('http://www.example.com'); + Cu.evalInSandbox(toEval, sbContent, '1.7'); + checkIterator(Cu.waiveXrays(sbContent.customIterator)); +} diff --git a/js/xpconnect/tests/unit/xpcshell.ini b/js/xpconnect/tests/unit/xpcshell.ini new file mode 100644 index 000000000..99d44b975 --- /dev/null +++ b/js/xpconnect/tests/unit/xpcshell.ini @@ -0,0 +1,137 @@ +[DEFAULT] +head = +tail = +support-files = + CatRegistrationComponents.manifest + bogus_element_type.jsm + bogus_exports_type.jsm + bug451678_subscript.js + component-blob.js + component-blob.manifest + component-file.js + component-file.manifest + component_import.js + component_import.manifest + importer.jsm + recursive_importA.jsm + recursive_importB.jsm + subScriptWithEarlyError.js + syntax_error.jsm + +[test_allowWaivers.js] +[test_bogus_files.js] +[test_bug408412.js] +[test_bug451678.js] +[test_bug604362.js] +[test_bug677864.js] +[test_bug711404.js] +[test_bug742444.js] +[test_bug778409.js] +[test_bug780370.js] +[test_bug809652.js] +[test_bug809674.js] +[test_bug813901.js] +[test_bug845201.js] +[test_bug845862.js] +[test_bug849730.js] +[test_bug851895.js] +[test_bug853709.js] +[test_bug854558.js] +[test_bug856067.js] +[test_bug868675.js] +[test_bug867486.js] +[test_bug872772.js] +[test_bug885800.js] +[test_bug930091.js] +[test_bug976151.js] +[test_bug1001094.js] +[test_bug1021312.js] +[test_bug1033253.js] +[test_bug1033920.js] +[test_bug1033927.js] +[test_bug1034262.js] +[test_bug1082450.js] +[test_bug1081990.js] +[test_bug1110546.js] +[test_bug1131707.js] +[test_bug1150106.js] +[test_bug1150771.js] +[test_bug1151385.js] +[test_bug1170311.js] +[test_bug1244222.js] +[test_bug_442086.js] +[test_callFunctionWithAsyncStack.js] +[test_classesByID_instanceof.js] +[test_deepFreezeClone.js] +[test_file.js] +[test_blob.js] +[test_blob2.js] +[test_file2.js] +[test_import.js] +[test_import_fail.js] +[test_interposition.js] +[test_isModuleLoaded.js] +[test_js_weak_references.js] +[test_onGarbageCollection-01.js] +head = head_ongc.js +[test_onGarbageCollection-02.js] +head = head_ongc.js +[test_onGarbageCollection-03.js] +head = head_ongc.js +[test_onGarbageCollection-04.js] +head = head_ongc.js +[test_onGarbageCollection-05.js] +head = head_ongc.js +[test_reflect_parse.js] +[test_localeCompare.js] +# Bug 676965: test fails consistently on Android +fail-if = os == "android" +[test_recursive_import.js] +[test_xpcomutils.js] +[test_unload.js] +[test_attributes.js] +# Bug 676965: test fails consistently on Android +fail-if = os == "android" +[test_params.js] +# Bug 676965: test fails consistently on Android +fail-if = os == "android" +[test_tearoffs.js] +[test_want_components.js] +[test_components.js] +[test_allowedDomains.js] +[test_allowedDomainsXHR.js] +[test_nuke_sandbox.js] +[test_sandbox_metadata.js] +[test_exportFunction.js] +[test_promise.js] +[test_returncode.js] +skip-if = os == "android" # native test components aren't available on Android +[test_textDecoder.js] +[test_url.js] +[test_URLSearchParams.js] +[test_fileReader.js] +[test_crypto.js] +[test_css.js] +[test_rtcIdentityProvider.js] +[test_sandbox_atob.js] +[test_isProxy.js] +[test_getObjectPrincipal.js] +[test_sandbox_name.js] +[test_watchdog_enable.js] +head = head_watchdog.js +[test_watchdog_disable.js] +head = head_watchdog.js +[test_watchdog_toggle.js] +head = head_watchdog.js +[test_watchdog_default.js] +head = head_watchdog.js +[test_watchdog_hibernate.js] +head = head_watchdog.js +[test_weak_keys.js] +[test_writeToGlobalPrototype.js] +[test_xpcwn_tamperproof.js] +[test_xrayed_iterator.js] +[test_xray_SavedFrame.js] +[test_xray_SavedFrame-02.js] +[test_resolve_dead_promise.js] +[test_asyncLoadSubScriptError.js] diff --git a/js/xpconnect/wrappers/AccessCheck.cpp b/js/xpconnect/wrappers/AccessCheck.cpp new file mode 100644 index 000000000..085e7100e --- /dev/null +++ b/js/xpconnect/wrappers/AccessCheck.cpp @@ -0,0 +1,458 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AccessCheck.h" + +#include "nsJSPrincipals.h" +#include "nsGlobalWindow.h" + +#include "XPCWrapper.h" +#include "XrayWrapper.h" +#include "FilteringWrapper.h" + +#include "jsfriendapi.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/LocationBinding.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "nsIDOMWindowCollection.h" +#include "nsJSUtils.h" +#include "xpcprivate.h" + +using namespace mozilla; +using namespace JS; +using namespace js; + +namespace xpc { + +nsIPrincipal* +GetCompartmentPrincipal(JSCompartment* compartment) +{ + return nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment)); +} + +nsIPrincipal* +GetObjectPrincipal(JSObject* obj) +{ + return GetCompartmentPrincipal(js::GetObjectCompartment(obj)); +} + +// Does the principal of compartment a subsume the principal of compartment b? +bool +AccessCheck::subsumes(JSCompartment* a, JSCompartment* b) +{ + nsIPrincipal* aprin = GetCompartmentPrincipal(a); + nsIPrincipal* bprin = GetCompartmentPrincipal(b); + return aprin->Subsumes(bprin); +} + +bool +AccessCheck::subsumes(JSObject* a, JSObject* b) +{ + return subsumes(js::GetObjectCompartment(a), js::GetObjectCompartment(b)); +} + +// Same as above, but considering document.domain. +bool +AccessCheck::subsumesConsideringDomain(JSCompartment* a, JSCompartment* b) +{ + nsIPrincipal* aprin = GetCompartmentPrincipal(a); + nsIPrincipal* bprin = GetCompartmentPrincipal(b); + return aprin->SubsumesConsideringDomain(bprin); +} + +// Does the compartment of the wrapper subsumes the compartment of the wrappee? +bool +AccessCheck::wrapperSubsumes(JSObject* wrapper) +{ + MOZ_ASSERT(js::IsWrapper(wrapper)); + JSObject* wrapped = js::UncheckedUnwrap(wrapper); + return AccessCheck::subsumes(js::GetObjectCompartment(wrapper), + js::GetObjectCompartment(wrapped)); +} + +bool +AccessCheck::isChrome(JSCompartment* compartment) +{ + bool privileged; + nsIPrincipal* principal = GetCompartmentPrincipal(compartment); + return NS_SUCCEEDED(nsXPConnect::SecurityManager()->IsSystemPrincipal(principal, &privileged)) && privileged; +} + +bool +AccessCheck::isChrome(JSObject* obj) +{ + return isChrome(js::GetObjectCompartment(obj)); +} + +nsIPrincipal* +AccessCheck::getPrincipal(JSCompartment* compartment) +{ + return GetCompartmentPrincipal(compartment); +} + +// Hardcoded policy for cross origin property access. See the HTML5 Spec. +static bool +IsPermitted(CrossOriginObjectType type, JSFlatString* prop, bool set) +{ + size_t propLength = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(prop)); + if (!propLength) + return false; + + char16_t propChar0 = JS_GetFlatStringCharAt(prop, 0); + if (type == CrossOriginLocation) + return dom::LocationBinding::IsPermitted(prop, propChar0, set); + if (type == CrossOriginWindow) + return dom::WindowBinding::IsPermitted(prop, propChar0, set); + + return false; +} + +static bool +IsFrameId(JSContext* cx, JSObject* obj, jsid idArg) +{ + MOZ_ASSERT(!js::IsWrapper(obj)); + RootedId id(cx, idArg); + + nsGlobalWindow* win = WindowOrNull(obj); + if (!win) { + return false; + } + + nsCOMPtr col = win->GetFrames(); + if (!col) { + return false; + } + + nsCOMPtr domwin; + if (JSID_IS_INT(id)) { + col->Item(JSID_TO_INT(id), getter_AddRefs(domwin)); + } else if (JSID_IS_STRING(id)) { + nsAutoJSString idAsString; + if (!idAsString.init(cx, JSID_TO_STRING(id))) { + return false; + } + col->NamedItem(idAsString, getter_AddRefs(domwin)); + } + + return domwin != nullptr; +} + +CrossOriginObjectType +IdentifyCrossOriginObject(JSObject* obj) +{ + obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + const js::Class* clasp = js::GetObjectClass(obj); + MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(Jsvalify(clasp)), "shouldn't have a holder here"); + + if (clasp->name[0] == 'L' && !strcmp(clasp->name, "Location")) + return CrossOriginLocation; + if (clasp->name[0] == 'W' && !strcmp(clasp->name, "Window")) + return CrossOriginWindow; + + return CrossOriginOpaque; +} + +bool +AccessCheck::isCrossOriginAccessPermitted(JSContext* cx, HandleObject wrapper, HandleId id, + Wrapper::Action act) +{ + if (act == Wrapper::CALL) + return false; + + if (act == Wrapper::ENUMERATE) + return true; + + // For the case of getting a property descriptor, we allow if either GET or SET + // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors. + if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) { + return isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::GET) || + isCrossOriginAccessPermitted(cx, wrapper, id, Wrapper::SET); + } + + RootedObject obj(cx, js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false)); + CrossOriginObjectType type = IdentifyCrossOriginObject(obj); + if (JSID_IS_STRING(id)) { + if (IsPermitted(type, JSID_TO_FLAT_STRING(id), act == Wrapper::SET)) + return true; + } else if (type != CrossOriginOpaque && + IsCrossOriginWhitelistedSymbol(cx, id)) { + // We always allow access to @@toStringTag, @@hasInstance, and + // @@isConcatSpreadable. But then we nerf them to be a value descriptor + // with value undefined in CrossOriginXrayWrapper. + return true; + } + + if (act != Wrapper::GET) + return false; + + // Check for frame IDs. If we're resolving named frames, make sure to only + // resolve ones that don't shadow native properties. See bug 860494. + if (type == CrossOriginWindow) { + if (JSID_IS_STRING(id)) { + bool wouldShadow = false; + if (!XrayUtils::HasNativeProperty(cx, wrapper, id, &wouldShadow) || + wouldShadow) + { + // If the named subframe matches the name of a DOM constructor, + // the global resolve triggered by the HasNativeProperty call + // above will try to perform a CheckedUnwrap on |wrapper|, and + // throw a security error if it fails. That exception isn't + // really useful for our callers, so we silence it and just + // deny access to the property (since it matched a builtin). + // + // Note that this would be a problem if the resolve code ever + // tried to CheckedUnwrap the wrapper _before_ concluding that + // the name corresponds to a builtin global property, since it + // would mean that we'd never permit cross-origin named subframe + // access (something we regrettably need to support). + JS_ClearPendingException(cx); + return false; + } + } + return IsFrameId(cx, obj, id); + } + return false; +} + +bool +AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, HandleValue v) +{ + // Primitives are fine. + if (!v.isObject()) + return true; + RootedObject obj(cx, &v.toObject()); + + // Non-wrappers are fine. + if (!js::IsWrapper(obj)) + return true; + + // CPOWs use COWs (in the unprivileged junk scope) for all child->parent + // references. Without this test, the child process wouldn't be able to + // pass any objects at all to CPOWs. + if (mozilla::jsipc::IsWrappedCPOW(obj) && + js::GetObjectCompartment(wrapper) == js::GetObjectCompartment(xpc::UnprivilegedJunkScope()) && + XRE_IsParentProcess()) + { + return true; + } + + // COWs are fine to pass to chrome if and only if they have __exposedProps__, + // since presumably content should never have a reason to pass an opaque + // object back to chrome. + if (AccessCheck::isChrome(js::UncheckedUnwrap(wrapper)) && WrapperFactory::IsCOW(obj)) { + RootedObject target(cx, js::UncheckedUnwrap(obj)); + JSAutoCompartment ac(cx, target); + RootedId id(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS)); + bool found = false; + if (!JS_HasPropertyById(cx, target, id, &found)) + return false; + if (found) + return true; + } + + // Same-origin wrappers are fine. + if (AccessCheck::wrapperSubsumes(obj)) + return true; + + // Badness. + JS_ReportErrorASCII(cx, "Permission denied to pass object to privileged code"); + return false; +} + +bool +AccessCheck::checkPassToPrivilegedCode(JSContext* cx, HandleObject wrapper, const CallArgs& args) +{ + if (!checkPassToPrivilegedCode(cx, wrapper, args.thisv())) + return false; + for (size_t i = 0; i < args.length(); ++i) { + if (!checkPassToPrivilegedCode(cx, wrapper, args[i])) + return false; + } + return true; +} + +enum Access { READ = (1<<0), WRITE = (1<<1), NO_ACCESS = 0 }; + +static void +EnterAndThrowASCII(JSContext* cx, JSObject* wrapper, const char* msg) +{ + JSAutoCompartment ac(cx, wrapper); + JS_ReportErrorASCII(cx, "%s", msg); +} + +bool +ExposedPropertiesOnly::check(JSContext* cx, HandleObject wrapper, HandleId id, Wrapper::Action act) +{ + RootedObject wrappedObject(cx, Wrapper::wrappedObject(wrapper)); + + if (act == Wrapper::CALL) + return false; + + // For the case of getting a property descriptor, we allow if either GET or SET + // is allowed, and rely on FilteringWrapper to filter out any disallowed accessors. + if (act == Wrapper::GET_PROPERTY_DESCRIPTOR) { + return check(cx, wrapper, id, Wrapper::GET) || + check(cx, wrapper, id, Wrapper::SET); + } + + RootedId exposedPropsId(cx, GetJSIDByIndex(cx, XPCJSContext::IDX_EXPOSEDPROPS)); + + // We need to enter the wrappee's compartment to look at __exposedProps__, + // but we want to be in the wrapper's compartment if we call Deny(). + // + // Unfortunately, |cx| can be in either compartment when we call ::check. :-( + JSAutoCompartment ac(cx, wrappedObject); + + bool found = false; + if (!JS_HasPropertyById(cx, wrappedObject, exposedPropsId, &found)) + return false; + + // If no __exposedProps__ existed, deny access. + if (!found) { + // Previously we automatically granted access to indexed properties and + // .length for Array COWs. We're not doing that anymore, so make sure to + // let people know what's going on. + bool isArray; + if (!JS_IsArrayObject(cx, wrappedObject, &isArray)) + return false; + if (!isArray) + isArray = JS_IsTypedArrayObject(wrappedObject); + bool isIndexedAccessOnArray = isArray && JSID_IS_INT(id) && JSID_TO_INT(id) >= 0; + bool isLengthAccessOnArray = isArray && JSID_IS_STRING(id) && + JS_FlatStringEqualsAscii(JSID_TO_FLAT_STRING(id), "length"); + if (isIndexedAccessOnArray || isLengthAccessOnArray) { + JSAutoCompartment ac2(cx, wrapper); + ReportWrapperDenial(cx, id, WrapperDenialForCOW, + "Access to elements and length of privileged Array not permitted"); + } + + return false; + } + + if (id == JSID_VOID) + return true; + + Rooted desc(cx); + if (!JS_GetPropertyDescriptorById(cx, wrappedObject, exposedPropsId, &desc)) + return false; + + if (!desc.object()) + return false; + + if (desc.hasGetterOrSetter()) { + EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be a value property"); + return false; + } + + RootedValue exposedProps(cx, desc.value()); + if (exposedProps.isNullOrUndefined()) + return false; + + if (!exposedProps.isObject()) { + EnterAndThrowASCII(cx, wrapper, "__exposedProps__ must be undefined, null, or an Object"); + return false; + } + + RootedObject hallpass(cx, &exposedProps.toObject()); + + if (!AccessCheck::subsumes(js::UncheckedUnwrap(hallpass), wrappedObject)) { + EnterAndThrowASCII(cx, wrapper, "Invalid __exposedProps__"); + return false; + } + + Access access = NO_ACCESS; + + if (!JS_GetPropertyDescriptorById(cx, hallpass, id, &desc)) { + return false; // Error + } + if (!desc.object() || !desc.enumerable()) + return false; + + if (!desc.value().isString()) { + EnterAndThrowASCII(cx, wrapper, "property must be a string"); + return false; + } + + JSFlatString* flat = JS_FlattenString(cx, desc.value().toString()); + if (!flat) + return false; + + size_t length = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(flat)); + + for (size_t i = 0; i < length; ++i) { + char16_t ch = JS_GetFlatStringCharAt(flat, i); + switch (ch) { + case 'r': + if (access & READ) { + EnterAndThrowASCII(cx, wrapper, "duplicate 'readable' property flag"); + return false; + } + access = Access(access | READ); + break; + + case 'w': + if (access & WRITE) { + EnterAndThrowASCII(cx, wrapper, "duplicate 'writable' property flag"); + return false; + } + access = Access(access | WRITE); + break; + + default: + EnterAndThrowASCII(cx, wrapper, "properties can only be readable or read and writable"); + return false; + } + } + + if (access == NO_ACCESS) { + EnterAndThrowASCII(cx, wrapper, "specified properties must have a permission bit set"); + return false; + } + + if ((act == Wrapper::SET && !(access & WRITE)) || + (act != Wrapper::SET && !(access & READ))) { + return false; + } + + // Inspect the property on the underlying object to check for red flags. + if (!JS_GetPropertyDescriptorById(cx, wrappedObject, id, &desc)) + return false; + + // Reject accessor properties. + if (desc.hasGetterOrSetter()) { + EnterAndThrowASCII(cx, wrapper, "Exposing privileged accessor properties is prohibited"); + return false; + } + + // Reject privileged or cross-origin callables. + if (desc.value().isObject()) { + RootedObject maybeCallable(cx, js::UncheckedUnwrap(&desc.value().toObject())); + if (JS::IsCallable(maybeCallable) && !AccessCheck::subsumes(wrapper, maybeCallable)) { + EnterAndThrowASCII(cx, wrapper, "Exposing privileged or cross-origin callable is prohibited"); + return false; + } + } + + return true; +} + +bool +ExposedPropertiesOnly::deny(js::Wrapper::Action act, HandleId id) +{ + // Fail silently for GET, ENUMERATE, and GET_PROPERTY_DESCRIPTOR. + if (act == js::Wrapper::GET || act == js::Wrapper::ENUMERATE || + act == js::Wrapper::GET_PROPERTY_DESCRIPTOR) + { + AutoJSContext cx; + return ReportWrapperDenial(cx, id, WrapperDenialForCOW, + "Access to privileged JS object not permitted"); + } + + return false; +} + +} // namespace xpc diff --git a/js/xpconnect/wrappers/AccessCheck.h b/js/xpconnect/wrappers/AccessCheck.h new file mode 100644 index 000000000..488cceac0 --- /dev/null +++ b/js/xpconnect/wrappers/AccessCheck.h @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __AccessCheck_h__ +#define __AccessCheck_h__ + +#include "jswrapper.h" +#include "js/Id.h" + +class nsIPrincipal; + +namespace xpc { + +class AccessCheck { + public: + static bool subsumes(JSCompartment* a, JSCompartment* b); + static bool subsumes(JSObject* a, JSObject* b); + static bool wrapperSubsumes(JSObject* wrapper); + static bool subsumesConsideringDomain(JSCompartment* a, JSCompartment* b); + static bool isChrome(JSCompartment* compartment); + static bool isChrome(JSObject* obj); + static nsIPrincipal* getPrincipal(JSCompartment* compartment); + static bool isCrossOriginAccessPermitted(JSContext* cx, JS::HandleObject obj, + JS::HandleId id, js::Wrapper::Action act); + static bool checkPassToPrivilegedCode(JSContext* cx, JS::HandleObject wrapper, + JS::HandleValue value); + static bool checkPassToPrivilegedCode(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args); +}; + +enum CrossOriginObjectType { + CrossOriginWindow, + CrossOriginLocation, + CrossOriginOpaque +}; +CrossOriginObjectType IdentifyCrossOriginObject(JSObject* obj); + +struct Policy { + static bool checkCall(JSContext* cx, JS::HandleObject wrapper, const JS::CallArgs& args) { + MOZ_CRASH("As a rule, filtering wrappers are non-callable"); + } +}; + +// This policy allows no interaction with the underlying callable. Everything throws. +struct Opaque : public Policy { + static bool check(JSContext* cx, JSObject* wrapper, jsid id, js::Wrapper::Action act) { + return false; + } + static bool deny(js::Wrapper::Action act, JS::HandleId id) { + return false; + } + static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) { + return false; + } +}; + +// Like the above, but allows CALL. +struct OpaqueWithCall : public Policy { + static bool check(JSContext* cx, JSObject* wrapper, jsid id, js::Wrapper::Action act) { + return act == js::Wrapper::CALL; + } + static bool deny(js::Wrapper::Action act, JS::HandleId id) { + return false; + } + static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) { + return false; + } + static bool checkCall(JSContext* cx, JS::HandleObject wrapper, const JS::CallArgs& args) { + return AccessCheck::checkPassToPrivilegedCode(cx, wrapper, args); + } +}; + +// This policy only permits access to properties that are safe to be used +// across origins. +struct CrossOriginAccessiblePropertiesOnly : public Policy { + static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act) { + return AccessCheck::isCrossOriginAccessPermitted(cx, wrapper, id, act); + } + static bool deny(js::Wrapper::Action act, JS::HandleId id) { + // Silently fail for enumerate-like operations. + if (act == js::Wrapper::ENUMERATE) + return true; + return false; + } + static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) { + return false; + } +}; + +// This policy only permits access to properties if they appear in the +// objects exposed properties list. +struct ExposedPropertiesOnly : public Policy { + static bool check(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, js::Wrapper::Action act); + + static bool deny(js::Wrapper::Action act, JS::HandleId id); + static bool allowNativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl) { + return false; + } +}; + +} // namespace xpc + +#endif /* __AccessCheck_h__ */ diff --git a/js/xpconnect/wrappers/AddonWrapper.cpp b/js/xpconnect/wrappers/AddonWrapper.cpp new file mode 100644 index 000000000..eb1670b3a --- /dev/null +++ b/js/xpconnect/wrappers/AddonWrapper.cpp @@ -0,0 +1,270 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "AddonWrapper.h" +#include "WrapperFactory.h" +#include "XrayWrapper.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "nsIAddonInterposition.h" +#include "xpcprivate.h" +#include "mozilla/dom/BindingUtils.h" +#include "nsGlobalWindow.h" + +#include "GeckoProfiler.h" + +#include "nsID.h" + +using namespace js; +using namespace JS; + +namespace xpc { + +bool +InterposeProperty(JSContext* cx, HandleObject target, const nsIID* iid, HandleId id, + MutableHandle descriptor) +{ + // We only want to do interpostion on DOM instances and + // wrapped natives. + RootedObject unwrapped(cx, UncheckedUnwrap(target)); + const js::Class* clasp = js::GetObjectClass(unwrapped); + bool isCPOW = jsipc::IsWrappedCPOW(unwrapped); + if (!mozilla::dom::IsDOMClass(clasp) && + !IS_WN_CLASS(clasp) && + !IS_PROTO_CLASS(clasp) && + clasp != &OuterWindowProxyClass && + !isCPOW) { + return true; + } + + XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(cx)); + MOZ_ASSERT(scope->HasInterposition()); + + nsCOMPtr interp = scope->GetInterposition(); + InterpositionWhitelist* wl = XPCWrappedNativeScope::GetInterpositionWhitelist(interp); + // We do InterposeProperty only if the id is on the whitelist of the interpostion + // or if the target is a CPOW. + if ((!wl || !wl->has(JSID_BITS(id.get()))) && !isCPOW) + return true; + + JSAddonId* addonId = AddonIdOfObject(target); + RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId))); + RootedValue prop(cx, IdToValue(id)); + RootedValue targetValue(cx, ObjectValue(*target)); + RootedValue descriptorVal(cx); + nsresult rv = interp->InterposeProperty(addonIdValue, targetValue, + iid, prop, &descriptorVal); + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + if (!descriptorVal.isObject()) + return true; + + // We need to be careful parsing descriptorVal. |cx| is in the compartment + // of the add-on and the descriptor is in the compartment of the + // interposition. We could wrap the descriptor in the add-on's compartment + // and then parse it. However, parsing the descriptor fetches properties + // from it, and we would try to interpose on those property accesses. So + // instead we parse in the interposition's compartment and then wrap the + // descriptor. + + { + JSAutoCompartment ac(cx, &descriptorVal.toObject()); + if (!JS::ObjectToCompletePropertyDescriptor(cx, target, descriptorVal, descriptor)) + return false; + } + + // Always make the property non-configurable regardless of what the + // interposition wants. + descriptor.setAttributes(descriptor.attributes() | JSPROP_PERMANENT); + + if (!JS_WrapPropertyDescriptor(cx, descriptor)) + return false; + + return true; +} + +bool +InterposeCall(JSContext* cx, JS::HandleObject target, const JS::CallArgs& args, bool* done) +{ + *done = false; + XPCWrappedNativeScope* scope = ObjectScope(CurrentGlobalOrNull(cx)); + MOZ_ASSERT(scope->HasInterposition()); + + nsCOMPtr interp = scope->GetInterposition(); + + RootedObject unwrappedTarget(cx, UncheckedUnwrap(target)); + XPCWrappedNativeScope* targetScope = ObjectScope(unwrappedTarget); + bool hasInterpostion = targetScope->HasCallInterposition(); + + if (!hasInterpostion) + return true; + + // If there is a call interpostion, we don't want to propogate the + // call to Base: + *done = true; + + JSAddonId* addonId = AddonIdOfObject(target); + RootedValue addonIdValue(cx, StringValue(StringOfAddonId(addonId))); + RootedValue targetValue(cx, ObjectValue(*target)); + RootedValue thisValue(cx, args.thisv()); + RootedObject argsArray(cx, ConvertArgsToArray(cx, args)); + if (!argsArray) + return false; + + RootedValue argsVal(cx, ObjectValue(*argsArray)); + RootedValue returnVal(cx); + + nsresult rv = interp->InterposeCall(addonIdValue, targetValue, + thisValue, argsVal, args.rval()); + if (NS_FAILED(rv)) { + xpc::Throw(cx, rv); + return false; + } + + return true; +} + +template +bool AddonWrapper::call(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const +{ + bool done = false; + if (!InterposeCall(cx, wrapper, args, &done)) + return false; + + return done || Base::call(cx, wrapper, args); +} + +template +bool +AddonWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, + HandleId id, MutableHandle desc) const +{ + if (!InterposeProperty(cx, wrapper, nullptr, id, desc)) + return false; + + if (desc.object()) + return true; + + return Base::getPropertyDescriptor(cx, wrapper, id, desc); +} + +template +bool +AddonWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, + HandleId id, MutableHandle desc) const +{ + if (!InterposeProperty(cx, wrapper, nullptr, id, desc)) + return false; + + if (desc.object()) + return true; + + return Base::getOwnPropertyDescriptor(cx, wrapper, id, desc); +} + +template +bool +AddonWrapper::get(JSContext* cx, JS::Handle wrapper, JS::Handle receiver, + JS::Handle id, JS::MutableHandle vp) const +{ + PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); + + Rooted desc(cx); + if (!InterposeProperty(cx, wrapper, nullptr, id, &desc)) + return false; + + if (!desc.object()) + return Base::get(cx, wrapper, receiver, id, vp); + + if (desc.getter()) { + return Call(cx, receiver, desc.getterObject(), HandleValueArray::empty(), vp); + } else { + vp.set(desc.value()); + return true; + } +} + +template +bool +AddonWrapper::set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v, + JS::HandleValue receiver, JS::ObjectOpResult& result) const +{ + Rooted desc(cx); + if (!InterposeProperty(cx, wrapper, nullptr, id, &desc)) + return false; + + if (!desc.object()) + return Base::set(cx, wrapper, id, v, receiver, result); + + if (desc.setter()) { + MOZ_ASSERT(desc.hasSetterObject()); + JS::AutoValueVector args(cx); + if (!args.append(v)) + return false; + RootedValue fval(cx, ObjectValue(*desc.setterObject())); + RootedValue ignored(cx); + if (!JS::Call(cx, receiver, fval, args, &ignored)) + return false; + return result.succeed(); + } + + return result.failCantSetInterposed(); +} + +template +bool +AddonWrapper::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id, + Handle desc, + ObjectOpResult& result) const +{ + Rooted interpDesc(cx); + if (!InterposeProperty(cx, wrapper, nullptr, id, &interpDesc)) + return false; + + if (!interpDesc.object()) + return Base::defineProperty(cx, wrapper, id, desc, result); + + js::ReportASCIIErrorWithId(cx, "unable to modify interposed property %s", id); + return false; +} + +template +bool +AddonWrapper::delete_(JSContext* cx, HandleObject wrapper, HandleId id, + ObjectOpResult& result) const +{ + Rooted desc(cx); + if (!InterposeProperty(cx, wrapper, nullptr, id, &desc)) + return false; + + if (!desc.object()) + return Base::delete_(cx, wrapper, id, result); + + js::ReportASCIIErrorWithId(cx, "unable to delete interposed property %s", id); + return false; +} + +#define AddonWrapperCC AddonWrapper +#define AddonWrapperXrayXPCWN AddonWrapper +#define AddonWrapperXrayDOM AddonWrapper + +template<> const AddonWrapperCC AddonWrapperCC::singleton(0); +template<> const AddonWrapperXrayXPCWN AddonWrapperXrayXPCWN::singleton(0); +template<> const AddonWrapperXrayDOM AddonWrapperXrayDOM::singleton(0); + +template class AddonWrapperCC; +template class AddonWrapperXrayXPCWN; +template class AddonWrapperXrayDOM; + +#undef AddonWrapperCC +#undef AddonWrapperXrayXPCWN +#undef AddonWrapperXrayDOM + +} // namespace xpc diff --git a/js/xpconnect/wrappers/AddonWrapper.h b/js/xpconnect/wrappers/AddonWrapper.h new file mode 100644 index 000000000..57d4d92af --- /dev/null +++ b/js/xpconnect/wrappers/AddonWrapper.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef AddonWrapper_h +#define AddonWrapper_h + +#include "mozilla/Attributes.h" + +#include "nsID.h" + +#include "jswrapper.h" + +namespace xpc { + +bool +InterposeProperty(JSContext* cx, JS::HandleObject target, const nsIID* iid, JS::HandleId id, + JS::MutableHandle descriptor); + +bool +InterposeCall(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, bool& done); + +template +class AddonWrapper : public Base { + public: + explicit constexpr AddonWrapper(unsigned flags) : Base(flags) { } + + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool defineProperty(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::Handle desc, + JS::ObjectOpResult& result) const override; + virtual bool delete_(JSContext* cx, JS::HandleObject proxy, JS::HandleId id, + JS::ObjectOpResult& result) const override; + virtual bool get(JSContext* cx, JS::Handle wrapper, JS::Handle receiver, + JS::Handle id, JS::MutableHandle vp) const override; + virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::HandleValue v, + JS::HandleValue receiver, JS::ObjectOpResult& result) const override; + virtual bool call(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + + virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + + static const AddonWrapper singleton; +}; + +} // namespace xpc + +#endif // AddonWrapper_h diff --git a/js/xpconnect/wrappers/ChromeObjectWrapper.cpp b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp new file mode 100644 index 000000000..7c42f17e1 --- /dev/null +++ b/js/xpconnect/wrappers/ChromeObjectWrapper.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "ChromeObjectWrapper.h" +#include "WrapperFactory.h" +#include "AccessCheck.h" +#include "xpcprivate.h" +#include "jsapi.h" +#include "jswrapper.h" +#include "nsXULAppAPI.h" + +using namespace JS; + +namespace xpc { + +const ChromeObjectWrapper ChromeObjectWrapper::singleton; + +bool +ChromeObjectWrapper::defineProperty(JSContext* cx, HandleObject wrapper, + HandleId id, + Handle desc, + ObjectOpResult& result) const +{ + if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, desc.value())) + return false; + return ChromeObjectWrapperBase::defineProperty(cx, wrapper, id, desc, result); +} + +bool +ChromeObjectWrapper::set(JSContext* cx, HandleObject wrapper, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result) const +{ + if (!AccessCheck::checkPassToPrivilegedCode(cx, wrapper, v)) + return false; + return ChromeObjectWrapperBase::set(cx, wrapper, id, v, receiver, result); +} + +} // namespace xpc diff --git a/js/xpconnect/wrappers/ChromeObjectWrapper.h b/js/xpconnect/wrappers/ChromeObjectWrapper.h new file mode 100644 index 000000000..8b273e470 --- /dev/null +++ b/js/xpconnect/wrappers/ChromeObjectWrapper.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __ChromeObjectWrapper_h__ +#define __ChromeObjectWrapper_h__ + +#include "mozilla/Attributes.h" + +#include "FilteringWrapper.h" + +namespace xpc { + +struct ExposedPropertiesOnly; + +// When a vanilla chrome JS object is exposed to content, we use a wrapper that +// supports __exposedProps__ for legacy reasons. For extra security, we override +// the traps that allow content to pass an object to chrome, and perform extra +// security checks on them. +#define ChromeObjectWrapperBase \ + FilteringWrapper + +class ChromeObjectWrapper : public ChromeObjectWrapperBase +{ + public: + constexpr ChromeObjectWrapper() : ChromeObjectWrapperBase(0) {} + + virtual bool defineProperty(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult& result) const override; + virtual bool set(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::HandleValue v, JS::HandleValue receiver, + JS::ObjectOpResult& result) const override; + + static const ChromeObjectWrapper singleton; +}; + +} /* namespace xpc */ + +#endif /* __ChromeObjectWrapper_h__ */ diff --git a/js/xpconnect/wrappers/FilteringWrapper.cpp b/js/xpconnect/wrappers/FilteringWrapper.cpp new file mode 100644 index 000000000..fdb9931a6 --- /dev/null +++ b/js/xpconnect/wrappers/FilteringWrapper.cpp @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "FilteringWrapper.h" +#include "AccessCheck.h" +#include "ChromeObjectWrapper.h" +#include "XrayWrapper.h" + +#include "jsapi.h" + +using namespace JS; +using namespace js; + +namespace xpc { + +static JS::SymbolCode sCrossOriginWhitelistedSymbolCodes[] = { + JS::SymbolCode::toStringTag, + JS::SymbolCode::hasInstance, + JS::SymbolCode::isConcatSpreadable +}; + +bool +IsCrossOriginWhitelistedSymbol(JSContext* cx, JS::HandleId id) +{ + if (!JSID_IS_SYMBOL(id)) { + return false; + } + + JS::Symbol* symbol = JSID_TO_SYMBOL(id); + for (auto code : sCrossOriginWhitelistedSymbolCodes) { + if (symbol == JS::GetWellKnownSymbol(cx, code)) { + return true; + } + } + + return false; +} + +template +static bool +Filter(JSContext* cx, HandleObject wrapper, AutoIdVector& props) +{ + size_t w = 0; + RootedId id(cx); + for (size_t n = 0; n < props.length(); ++n) { + id = props[n]; + if (Policy::check(cx, wrapper, id, Wrapper::GET) || Policy::check(cx, wrapper, id, Wrapper::SET)) + props[w++].set(id); + else if (JS_IsExceptionPending(cx)) + return false; + } + if (!props.resize(w)) + return false; + + return true; +} + +template +static bool +FilterPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, MutableHandle desc) +{ + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + bool getAllowed = Policy::check(cx, wrapper, id, Wrapper::GET); + if (JS_IsExceptionPending(cx)) + return false; + bool setAllowed = Policy::check(cx, wrapper, id, Wrapper::SET); + if (JS_IsExceptionPending(cx)) + return false; + + MOZ_ASSERT(getAllowed || setAllowed, + "Filtering policy should not allow GET_PROPERTY_DESCRIPTOR in this case"); + + if (!desc.hasGetterOrSetter()) { + // Handle value properties. + if (!getAllowed) + desc.value().setUndefined(); + } else { + // Handle accessor properties. + MOZ_ASSERT(desc.value().isUndefined()); + if (!getAllowed) + desc.setGetter(nullptr); + if (!setAllowed) + desc.setSetter(nullptr); + } + + return true; +} + +template +bool +FilteringWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, + HandleId id, + MutableHandle desc) const +{ + assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET | + BaseProxyHandler::GET_PROPERTY_DESCRIPTOR); + if (!Base::getPropertyDescriptor(cx, wrapper, id, desc)) + return false; + return FilterPropertyDescriptor(cx, wrapper, id, desc); +} + +template +bool +FilteringWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, + HandleId id, + MutableHandle desc) const +{ + assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET | + BaseProxyHandler::GET_PROPERTY_DESCRIPTOR); + if (!Base::getOwnPropertyDescriptor(cx, wrapper, id, desc)) + return false; + return FilterPropertyDescriptor(cx, wrapper, id, desc); +} + +template +bool +FilteringWrapper::ownPropertyKeys(JSContext* cx, HandleObject wrapper, + AutoIdVector& props) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); + return Base::ownPropertyKeys(cx, wrapper, props) && + Filter(cx, wrapper, props); +} + +template +bool +FilteringWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, + HandleObject wrapper, + AutoIdVector& props) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); + return Base::getOwnEnumerablePropertyKeys(cx, wrapper, props) && + Filter(cx, wrapper, props); +} + +template +bool +FilteringWrapper::enumerate(JSContext* cx, HandleObject wrapper, + MutableHandleObject objp) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); + // We refuse to trigger the enumerate hook across chrome wrappers because + // we don't know how to censor custom iterator objects. Instead we trigger + // the default proxy enumerate trap, which will use js::GetPropertyKeys + // for the list of (censored) ids. + return js::BaseProxyHandler::enumerate(cx, wrapper, objp); +} + +template +bool +FilteringWrapper::call(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const +{ + if (!Policy::checkCall(cx, wrapper, args)) + return false; + return Base::call(cx, wrapper, args); +} + +template +bool +FilteringWrapper::construct(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const +{ + if (!Policy::checkCall(cx, wrapper, args)) + return false; + return Base::construct(cx, wrapper, args); +} + +template +bool +FilteringWrapper::nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, const JS::CallArgs& args) const +{ + if (Policy::allowNativeCall(cx, test, impl)) + return Base::Permissive::nativeCall(cx, test, impl, args); + return Base::Restrictive::nativeCall(cx, test, impl, args); +} + +template +bool +FilteringWrapper::getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleObject protop) const +{ + // Filtering wrappers do not allow access to the prototype. + protop.set(nullptr); + return true; +} + +template +bool +FilteringWrapper::enter(JSContext* cx, HandleObject wrapper, + HandleId id, Wrapper::Action act, bool* bp) const +{ + if (!Policy::check(cx, wrapper, id, act)) { + *bp = JS_IsExceptionPending(cx) ? false : Policy::deny(act, id); + return false; + } + *bp = true; + return true; +} + +bool +CrossOriginXrayWrapper::getPropertyDescriptor(JSContext* cx, + JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const +{ + if (!SecurityXrayDOM::getPropertyDescriptor(cx, wrapper, id, desc)) + return false; + if (desc.object()) { + // Cross-origin DOM objects do not have symbol-named properties apart + // from the ones we add ourselves here. + MOZ_ASSERT(!JSID_IS_SYMBOL(id), + "What's this symbol-named property that appeared on a " + "Window or Location instance?"); + + // All properties on cross-origin DOM objects are |own|. + desc.object().set(wrapper); + + // All properties on cross-origin DOM objects are non-enumerable and + // "configurable". Any value attributes are read-only. + desc.attributesRef() &= ~JSPROP_ENUMERATE; + desc.attributesRef() &= ~JSPROP_PERMANENT; + if (!desc.getter() && !desc.setter()) + desc.attributesRef() |= JSPROP_READONLY; + } else if (IsCrossOriginWhitelistedSymbol(cx, id)) { + // Spec says to return PropertyDescriptor { + // [[Value]]: undefined, [[Writable]]: false, [[Enumerable]]: false, + // [[Configurable]]: true + // }. + // + desc.setDataDescriptor(JS::UndefinedHandleValue, JSPROP_READONLY); + desc.object().set(wrapper); + } + + return true; +} + +bool +CrossOriginXrayWrapper::getOwnPropertyDescriptor(JSContext* cx, + JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const +{ + // All properties on cross-origin DOM objects are |own|. + return getPropertyDescriptor(cx, wrapper, id, desc); +} + +bool +CrossOriginXrayWrapper::ownPropertyKeys(JSContext* cx, JS::Handle wrapper, + JS::AutoIdVector& props) const +{ + // All properties on cross-origin objects are supposed |own|, despite what + // the underlying native object may report. Override the inherited trap to + // avoid passing JSITER_OWNONLY as a flag. + if (!SecurityXrayDOM::getPropertyKeys(cx, wrapper, JSITER_HIDDEN, props)) { + return false; + } + + // Now add the three symbol-named props cross-origin objects have. +#ifdef DEBUG + for (size_t n = 0; n < props.length(); ++n) { + MOZ_ASSERT(!JSID_IS_SYMBOL(props[n]), + "Unexpected existing symbol-name prop"); + } +#endif + if (!props.reserve(props.length() + + ArrayLength(sCrossOriginWhitelistedSymbolCodes))) { + return false; + } + + for (auto code : sCrossOriginWhitelistedSymbolCodes) { + props.infallibleAppend(SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, code))); + } + + return true; +} + +bool +CrossOriginXrayWrapper::defineProperty(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult& result) const +{ + JS_ReportErrorASCII(cx, "Permission denied to define property on cross-origin object"); + return false; +} + +bool +CrossOriginXrayWrapper::delete_(JSContext* cx, JS::Handle wrapper, + JS::Handle id, JS::ObjectOpResult& result) const +{ + JS_ReportErrorASCII(cx, "Permission denied to delete property on cross-origin object"); + return false; +} + +#define XOW FilteringWrapper +#define NNXOW FilteringWrapper +#define NNXOWC FilteringWrapper + +template<> const XOW XOW::singleton(0); +template<> const NNXOW NNXOW::singleton(0); +template<> const NNXOWC NNXOWC::singleton(0); + +template class XOW; +template class NNXOW; +template class NNXOWC; +template class ChromeObjectWrapperBase; +} // namespace xpc diff --git a/js/xpconnect/wrappers/FilteringWrapper.h b/js/xpconnect/wrappers/FilteringWrapper.h new file mode 100644 index 000000000..1e1691360 --- /dev/null +++ b/js/xpconnect/wrappers/FilteringWrapper.h @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __FilteringWrapper_h__ +#define __FilteringWrapper_h__ + +#include "XrayWrapper.h" +#include "mozilla/Attributes.h" +#include "jswrapper.h" +#include "js/CallNonGenericMethod.h" + +namespace JS { +class AutoIdVector; +} // namespace JS + +namespace xpc { + +template +class FilteringWrapper : public Base { + public: + constexpr explicit FilteringWrapper(unsigned flags) : Base(flags) {} + + virtual bool enter(JSContext* cx, JS::Handle wrapper, JS::Handle id, + js::Wrapper::Action act, bool* bp) const override; + + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::Handle wrapper, + JS::AutoIdVector& props) const override; + + virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle wrapper, + JS::AutoIdVector& props) const override; + virtual bool enumerate(JSContext* cx, JS::Handle wrapper, + JS::MutableHandle objp) const override; + + virtual bool call(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + virtual bool construct(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, JS::NativeImpl impl, + const JS::CallArgs& args) const override; + + virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleObject protop) const override; + + static const FilteringWrapper singleton; +}; + +/* + * The HTML5 spec mandates very particular object behavior for cross-origin DOM + * objects (Window and Location), some of which runs contrary to the way that + * other XrayWrappers behave. We use this class to implement those semantics. + */ +class CrossOriginXrayWrapper : public SecurityXrayDOM { + public: + constexpr explicit CrossOriginXrayWrapper(unsigned flags) : + SecurityXrayDOM(flags) {} + + + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool defineProperty(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult& result) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::Handle wrapper, + JS::AutoIdVector& props) const override; + virtual bool delete_(JSContext* cx, JS::Handle wrapper, + JS::Handle id, JS::ObjectOpResult& result) const override; + + virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; +}; + +// Check whether the given jsid is a symbol whose value can be gotten +// cross-origin. Cross-origin gets always return undefined as the value. +bool IsCrossOriginWhitelistedSymbol(JSContext* cx, JS::HandleId id); + +} // namespace xpc + +#endif /* __FilteringWrapper_h__ */ diff --git a/js/xpconnect/wrappers/WaiveXrayWrapper.cpp b/js/xpconnect/wrappers/WaiveXrayWrapper.cpp new file mode 100644 index 000000000..27c010d34 --- /dev/null +++ b/js/xpconnect/wrappers/WaiveXrayWrapper.cpp @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WaiveXrayWrapper.h" +#include "WrapperFactory.h" +#include "jsapi.h" + +using namespace JS; + +namespace xpc { + +static bool +WaiveAccessors(JSContext* cx, MutableHandle desc) +{ + if (desc.hasGetterObject() && desc.getterObject()) { + RootedValue v(cx, JS::ObjectValue(*desc.getterObject())); + if (!WrapperFactory::WaiveXrayAndWrap(cx, &v)) + return false; + desc.setGetterObject(&v.toObject()); + } + + if (desc.hasSetterObject() && desc.setterObject()) { + RootedValue v(cx, JS::ObjectValue(*desc.setterObject())); + if (!WrapperFactory::WaiveXrayAndWrap(cx, &v)) + return false; + desc.setSetterObject(&v.toObject()); + } + return true; +} + +bool +WaiveXrayWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, + MutableHandle desc) const +{ + return CrossCompartmentWrapper::getPropertyDescriptor(cx, wrapper, id, desc) && + WrapperFactory::WaiveXrayAndWrap(cx, desc.value()) && WaiveAccessors(cx, desc); +} + +bool +WaiveXrayWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, + MutableHandle desc) const +{ + return CrossCompartmentWrapper::getOwnPropertyDescriptor(cx, wrapper, id, desc) && + WrapperFactory::WaiveXrayAndWrap(cx, desc.value()) && WaiveAccessors(cx, desc); +} + +bool +WaiveXrayWrapper::get(JSContext* cx, HandleObject wrapper, HandleValue receiver, HandleId id, + MutableHandleValue vp) const +{ + return CrossCompartmentWrapper::get(cx, wrapper, receiver, id, vp) && + WrapperFactory::WaiveXrayAndWrap(cx, vp); +} + +bool +WaiveXrayWrapper::enumerate(JSContext* cx, HandleObject proxy, + MutableHandleObject objp) const +{ + return CrossCompartmentWrapper::enumerate(cx, proxy, objp) && + WrapperFactory::WaiveXrayAndWrap(cx, objp); +} + +bool +WaiveXrayWrapper::call(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const +{ + return CrossCompartmentWrapper::call(cx, wrapper, args) && + WrapperFactory::WaiveXrayAndWrap(cx, args.rval()); +} + +bool +WaiveXrayWrapper::construct(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const +{ + return CrossCompartmentWrapper::construct(cx, wrapper, args) && + WrapperFactory::WaiveXrayAndWrap(cx, args.rval()); +} + +// NB: This is important as the other side of a handshake with FieldGetter. See +// nsXBLProtoImplField.cpp. +bool +WaiveXrayWrapper::nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, const JS::CallArgs& args) const +{ + return CrossCompartmentWrapper::nativeCall(cx, test, impl, args) && + WrapperFactory::WaiveXrayAndWrap(cx, args.rval()); +} + +bool +WaiveXrayWrapper::getPrototype(JSContext* cx, HandleObject wrapper, MutableHandleObject protop) const +{ + return CrossCompartmentWrapper::getPrototype(cx, wrapper, protop) && + (!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop)); +} + +bool +WaiveXrayWrapper::getPrototypeIfOrdinary(JSContext* cx, HandleObject wrapper, bool* isOrdinary, + MutableHandleObject protop) const +{ + return CrossCompartmentWrapper::getPrototypeIfOrdinary(cx, wrapper, isOrdinary, protop) && + (!protop || WrapperFactory::WaiveXrayAndWrap(cx, protop)); +} + +} // namespace xpc diff --git a/js/xpconnect/wrappers/WaiveXrayWrapper.h b/js/xpconnect/wrappers/WaiveXrayWrapper.h new file mode 100644 index 000000000..b0b447796 --- /dev/null +++ b/js/xpconnect/wrappers/WaiveXrayWrapper.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef __CrossOriginWrapper_h__ +#define __CrossOriginWrapper_h__ + +#include "mozilla/Attributes.h" + +#include "jswrapper.h" + +namespace xpc { + +class WaiveXrayWrapper : public js::CrossCompartmentWrapper { + public: + explicit constexpr WaiveXrayWrapper(unsigned flags) : js::CrossCompartmentWrapper(flags) { } + + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool getPrototype(JSContext* cx, JS::Handle wrapper, + JS::MutableHandle protop) const override; + virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle wrapper, + bool* isOrdinary, + JS::MutableHandle protop) const override; + virtual bool get(JSContext* cx, JS::Handle wrapper, JS::Handle receiver, + JS::Handle id, JS::MutableHandle vp) const override; + virtual bool call(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + virtual bool construct(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + + virtual bool enumerate(JSContext* cx, JS::Handle proxy, + JS::MutableHandle objp) const override; + virtual bool nativeCall(JSContext* cx, JS::IsAcceptableThis test, + JS::NativeImpl impl, const JS::CallArgs& args) const override; + virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle wrapper, + JS::Handle id, + JS::MutableHandle desc) const override; + + static const WaiveXrayWrapper singleton; +}; + +} // namespace xpc + +#endif diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp new file mode 100644 index 000000000..0031fb127 --- /dev/null +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -0,0 +1,671 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "WaiveXrayWrapper.h" +#include "FilteringWrapper.h" +#include "AddonWrapper.h" +#include "XrayWrapper.h" +#include "AccessCheck.h" +#include "XPCWrapper.h" +#include "ChromeObjectWrapper.h" +#include "WrapperFactory.h" + +#include "xpcprivate.h" +#include "XPCMaps.h" +#include "mozilla/dom/BindingUtils.h" +#include "jsfriendapi.h" +#include "mozilla/jsipc/CrossProcessObjectWrappers.h" +#include "mozilla/Likely.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsContentUtils.h" +#include "nsXULAppAPI.h" + +using namespace JS; +using namespace js; +using namespace mozilla; + +namespace xpc { + +// When chrome pulls a naked property across the membrane using +// .wrappedJSObject, we want it to cross the membrane into the +// chrome compartment without automatically being wrapped into an +// X-ray wrapper. We achieve this by wrapping it into a special +// transparent wrapper in the origin (non-chrome) compartment. When +// an object with that special wrapper applied crosses into chrome, +// we know to not apply an X-ray wrapper. +const Wrapper XrayWaiver(WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG); + +// When objects for which we waived the X-ray wrapper cross into +// chrome, we wrap them into a special cross-compartment wrapper +// that transitively extends the waiver to all properties we get +// off it. +const WaiveXrayWrapper WaiveXrayWrapper::singleton(0); + +bool +WrapperFactory::IsCOW(JSObject* obj) +{ + return IsWrapper(obj) && + Wrapper::wrapperHandler(obj) == &ChromeObjectWrapper::singleton; +} + +JSObject* +WrapperFactory::GetXrayWaiver(HandleObject obj) +{ + // Object should come fully unwrapped but outerized. + MOZ_ASSERT(obj == UncheckedUnwrap(obj)); + MOZ_ASSERT(!js::IsWindow(obj)); + XPCWrappedNativeScope* scope = ObjectScope(obj); + MOZ_ASSERT(scope); + + if (!scope->mWaiverWrapperMap) + return nullptr; + + return scope->mWaiverWrapperMap->Find(obj); +} + +JSObject* +WrapperFactory::CreateXrayWaiver(JSContext* cx, HandleObject obj) +{ + // The caller is required to have already done a lookup. + // NB: This implictly performs the assertions of GetXrayWaiver. + MOZ_ASSERT(!GetXrayWaiver(obj)); + XPCWrappedNativeScope* scope = ObjectScope(obj); + + JSAutoCompartment ac(cx, obj); + JSObject* waiver = Wrapper::New(cx, obj, &XrayWaiver); + if (!waiver) + return nullptr; + + // Add the new waiver to the map. It's important that we only ever have + // one waiver for the lifetime of the target object. + if (!scope->mWaiverWrapperMap) { + scope->mWaiverWrapperMap = + JSObject2JSObjectMap::newMap(XPC_WRAPPER_MAP_LENGTH); + } + if (!scope->mWaiverWrapperMap->Add(cx, obj, waiver)) + return nullptr; + return waiver; +} + +JSObject* +WrapperFactory::WaiveXray(JSContext* cx, JSObject* objArg) +{ + RootedObject obj(cx, objArg); + obj = UncheckedUnwrap(obj); + MOZ_ASSERT(!js::IsWindow(obj)); + + JSObject* waiver = GetXrayWaiver(obj); + if (!waiver) { + waiver = CreateXrayWaiver(cx, obj); + } + MOZ_ASSERT(!ObjectIsMarkedGray(waiver)); + return waiver; +} + +/* static */ bool +WrapperFactory::AllowWaiver(JSCompartment* target, JSCompartment* origin) +{ + return CompartmentPrivate::Get(target)->allowWaivers && + AccessCheck::subsumes(target, origin); +} + +/* static */ bool +WrapperFactory::AllowWaiver(JSObject* wrapper) { + MOZ_ASSERT(js::IsCrossCompartmentWrapper(wrapper)); + return AllowWaiver(js::GetObjectCompartment(wrapper), + js::GetObjectCompartment(js::UncheckedUnwrap(wrapper))); +} + +inline bool +ShouldWaiveXray(JSContext* cx, JSObject* originalObj) +{ + unsigned flags; + (void) js::UncheckedUnwrap(originalObj, /* stopAtWindowProxy = */ true, &flags); + + // If the original object did not point through an Xray waiver, we're done. + if (!(flags & WrapperFactory::WAIVE_XRAY_WRAPPER_FLAG)) + return false; + + // If the original object was not a cross-compartment wrapper, that means + // that the caller explicitly created a waiver. Preserve it so that things + // like WaiveXrayAndWrap work. + if (!(flags & Wrapper::CROSS_COMPARTMENT)) + return true; + + // Otherwise, this is a case of explicitly passing a wrapper across a + // compartment boundary. In that case, we only want to preserve waivers + // in transactions between same-origin compartments. + JSCompartment* oldCompartment = js::GetObjectCompartment(originalObj); + JSCompartment* newCompartment = js::GetContextCompartment(cx); + bool sameOrigin = + AccessCheck::subsumesConsideringDomain(oldCompartment, newCompartment) && + AccessCheck::subsumesConsideringDomain(newCompartment, oldCompartment); + return sameOrigin; +} + +void +WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope, + HandleObject objArg, HandleObject objectPassedToWrap, + MutableHandleObject retObj) +{ + bool waive = ShouldWaiveXray(cx, objectPassedToWrap); + RootedObject obj(cx, objArg); + retObj.set(nullptr); + // Outerize any raw inner objects at the entry point here, so that we don't + // have to worry about them for the rest of the wrapping code. + if (js::IsWindow(obj)) { + JSAutoCompartment ac(cx, obj); + obj = js::ToWindowProxyIfWindow(obj); + MOZ_ASSERT(obj); + // ToWindowProxyIfWindow can return a CCW if |obj| was a + // navigated-away-from Window. Strip any CCWs. + obj = js::UncheckedUnwrap(obj); + if (JS_IsDeadWrapper(obj)) { + JS_ReportErrorASCII(cx, "Can't wrap dead object"); + return; + } + MOZ_ASSERT(js::IsWindowProxy(obj)); + // We crossed a compartment boundary there, so may now have a gray + // object. This function is not allowed to return gray objects, so + // don't do that. + ExposeObjectToActiveJS(obj); + } + + // If we've got a WindowProxy, there's nothing special that needs to be + // done here, and we can move on to the next phase of wrapping. We handle + // this case first to allow us to assert against wrappers below. + if (js::IsWindowProxy(obj)) { + retObj.set(waive ? WaiveXray(cx, obj) : obj); + return; + } + + // Here are the rules for wrapping: + // We should never get a proxy here (the JS engine unwraps those for us). + MOZ_ASSERT(!IsWrapper(obj)); + + // Now, our object is ready to be wrapped, but several objects (notably + // nsJSIIDs) have a wrapper per scope. If we are about to wrap one of + // those objects in a security wrapper, then we need to hand back the + // wrapper for the new scope instead. Also, global objects don't move + // between scopes so for those we also want to return the wrapper. So... + if (!IS_WN_REFLECTOR(obj) || JS_IsGlobalObject(obj)) { + retObj.set(waive ? WaiveXray(cx, obj) : obj); + return; + } + + XPCWrappedNative* wn = XPCWrappedNative::Get(obj); + + JSAutoCompartment ac(cx, obj); + XPCCallContext ccx(cx, obj); + RootedObject wrapScope(cx, scope); + + { + if (NATIVE_HAS_FLAG(&ccx, WantPreCreate)) { + // We have a precreate hook. This object might enforce that we only + // ever create JS object for it. + + // Note: this penalizes objects that only have one wrapper, but are + // being accessed across compartments. We would really prefer to + // replace the above code with a test that says "do you only have one + // wrapper?" + nsresult rv = wn->GetScriptableInfo()->GetCallback()-> + PreCreate(wn->Native(), cx, scope, wrapScope.address()); + if (NS_FAILED(rv)) { + retObj.set(waive ? WaiveXray(cx, obj) : obj); + return; + } + + // If the handed back scope differs from the passed-in scope and is in + // a separate compartment, then this object is explicitly requesting + // that we don't create a second JS object for it: create a security + // wrapper. + if (js::GetObjectCompartment(scope) != js::GetObjectCompartment(wrapScope)) { + retObj.set(waive ? WaiveXray(cx, obj) : obj); + return; + } + + RootedObject currentScope(cx, JS_GetGlobalForObject(cx, obj)); + if (MOZ_UNLIKELY(wrapScope != currentScope)) { + // The wrapper claims it wants to be in the new scope, but + // currently has a reflection that lives in the old scope. This + // can mean one of two things, both of which are rare: + // + // 1 - The object has a PreCreate hook (we checked for it above), + // but is deciding to request one-wrapper-per-scope (rather than + // one-wrapper-per-native) for some reason. Usually, a PreCreate + // hook indicates one-wrapper-per-native. In this case we want to + // make a new wrapper in the new scope. + // + // 2 - We're midway through wrapper reparenting. The document has + // moved to a new scope, but |wn| hasn't been moved yet, and + // we ended up calling JS_WrapObject() on its JS object. In this + // case, we want to return the existing wrapper. + // + // So we do a trick: call PreCreate _again_, but say that we're + // wrapping for the old scope, rather than the new one. If (1) is + // the case, then PreCreate will return the scope we pass to it + // (the old scope). If (2) is the case, PreCreate will return the + // scope of the document (the new scope). + RootedObject probe(cx); + rv = wn->GetScriptableInfo()->GetCallback()-> + PreCreate(wn->Native(), cx, currentScope, probe.address()); + + // Check for case (2). + if (probe != currentScope) { + MOZ_ASSERT(probe == wrapScope); + retObj.set(waive ? WaiveXray(cx, obj) : obj); + return; + } + + // Ok, must be case (1). Fall through and create a new wrapper. + } + + // Nasty hack for late-breaking bug 781476. This will confuse identity checks, + // but it's probably better than any of our alternatives. + // + // Note: We have to ignore domain here. The JS engine assumes that, given a + // compartment c, if c->wrap(x) returns a cross-compartment wrapper at time t0, + // it will also return a cross-compartment wrapper for any time t1 > t0 unless + // an explicit transplant is performed. In particular, wrapper recomputation + // assumes that recomputing a wrapper will always result in a wrapper. + // + // This doesn't actually pose a security issue, because we'll still compute + // the correct (opaque) wrapper for the object below given the security + // characteristics of the two compartments. + if (!AccessCheck::isChrome(js::GetObjectCompartment(wrapScope)) && + AccessCheck::subsumes(js::GetObjectCompartment(wrapScope), + js::GetObjectCompartment(obj))) + { + retObj.set(waive ? WaiveXray(cx, obj) : obj); + return; + } + } + } + + // This public WrapNativeToJSVal API enters the compartment of 'wrapScope' + // so we don't have to. + RootedValue v(cx); + nsresult rv = + nsXPConnect::XPConnect()->WrapNativeToJSVal(cx, wrapScope, wn->Native(), nullptr, + &NS_GET_IID(nsISupports), false, &v); + if (NS_FAILED(rv)) { + return; + } + + obj.set(&v.toObject()); + MOZ_ASSERT(IS_WN_REFLECTOR(obj), "bad object"); + MOZ_ASSERT(!ObjectIsMarkedGray(obj), "Should never return gray reflectors"); + + // Because the underlying native didn't have a PreCreate hook, we had + // to a new (or possibly pre-existing) XPCWN in our compartment. + // This could be a problem for chrome code that passes XPCOM objects + // across compartments, because the effects of QI would disappear across + // compartments. + // + // So whenever we pull an XPCWN across compartments in this manner, we + // give the destination object the union of the two native sets. We try + // to do this cleverly in the common case to avoid too much overhead. + XPCWrappedNative* newwn = XPCWrappedNative::Get(obj); + RefPtr unionSet = XPCNativeSet::GetNewOrUsed(newwn->GetSet(), + wn->GetSet(), false); + if (!unionSet) { + return; + } + newwn->SetSet(unionSet.forget()); + + retObj.set(waive ? WaiveXray(cx, obj) : obj); +} + +#ifdef DEBUG +static void +DEBUG_CheckUnwrapSafety(HandleObject obj, const js::Wrapper* handler, + JSCompartment* origin, JSCompartment* target) +{ + if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) { + // If the caller is chrome (or effectively so), unwrap should always be allowed. + MOZ_ASSERT(!handler->hasSecurityPolicy()); + } else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) { + // Similarly, if this is a privileged scope that has opted to make itself + // accessible to the world (allowed only during automation), unwrap should + // be allowed. + MOZ_ASSERT(!handler->hasSecurityPolicy()); + } else { + // Otherwise, it should depend on whether the target subsumes the origin. + MOZ_ASSERT(handler->hasSecurityPolicy() == !AccessCheck::subsumesConsideringDomain(target, origin)); + } +} +#else +#define DEBUG_CheckUnwrapSafety(obj, handler, origin, target) {} +#endif + +static const Wrapper* +SelectWrapper(bool securityWrapper, bool wantXrays, XrayType xrayType, + bool waiveXrays, bool originIsXBLScope, JSObject* obj) +{ + // Waived Xray uses a modified CCW that has transparent behavior but + // transitively waives Xrays on arguments. + if (waiveXrays) { + MOZ_ASSERT(!securityWrapper); + return &WaiveXrayWrapper::singleton; + } + + // If we don't want or can't use Xrays, select a wrapper that's either + // entirely transparent or entirely opaque. + if (!wantXrays || xrayType == NotXray) { + if (!securityWrapper) + return &CrossCompartmentWrapper::singleton; + return &FilteringWrapper::singleton; + } + + // Ok, we're using Xray. If this isn't a security wrapper, use the permissive + // version and skip the filter. + if (!securityWrapper) { + if (xrayType == XrayForWrappedNative) + return &PermissiveXrayXPCWN::singleton; + else if (xrayType == XrayForDOMObject) + return &PermissiveXrayDOM::singleton; + else if (xrayType == XrayForJSObject) + return &PermissiveXrayJS::singleton; + MOZ_ASSERT(xrayType == XrayForOpaqueObject); + return &PermissiveXrayOpaque::singleton; + } + + // This is a security wrapper. Use the security versions and filter. + if (xrayType == XrayForDOMObject && IdentifyCrossOriginObject(obj) != CrossOriginOpaque) + return &FilteringWrapper::singleton; + + // There's never any reason to expose other objects to non-subsuming actors. + // Just use an opaque wrapper in these cases. + // + // In general, we don't want opaque function wrappers to be callable. + // But in the case of XBL, we rely on content being able to invoke + // functions exposed from the XBL scope. We could remove this exception, + // if needed, by using ExportFunction to generate the content-side + // representations of XBL methods. + if (xrayType == XrayForJSObject && originIsXBLScope) + return &FilteringWrapper::singleton; + return &FilteringWrapper::singleton; +} + +static const Wrapper* +SelectAddonWrapper(JSContext* cx, HandleObject obj, const Wrapper* wrapper) +{ + JSAddonId* originAddon = JS::AddonIdOfObject(obj); + JSAddonId* targetAddon = JS::AddonIdOfObject(JS::CurrentGlobalOrNull(cx)); + + MOZ_ASSERT(AccessCheck::isChrome(JS::CurrentGlobalOrNull(cx))); + MOZ_ASSERT(targetAddon); + + if (targetAddon == originAddon) + return wrapper; + + // Add-on interposition only supports certain wrapper types, so we check if + // we would have used one of the supported ones. + if (wrapper == &CrossCompartmentWrapper::singleton) + return &AddonWrapper::singleton; + else if (wrapper == &PermissiveXrayXPCWN::singleton) + return &AddonWrapper::singleton; + else if (wrapper == &PermissiveXrayDOM::singleton) + return &AddonWrapper::singleton; + + // |wrapper| is not supported for interposition, so we don't do it. + return wrapper; +} + +JSObject* +WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj) +{ + MOZ_ASSERT(!IsWrapper(obj) || + GetProxyHandler(obj) == &XrayWaiver || + js::IsWindowProxy(obj), + "wrapped object passed to rewrap"); + MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder"); + MOZ_ASSERT(!js::IsWindow(obj)); + MOZ_ASSERT(dom::IsJSAPIActive()); + + // Compute the information we need to select the right wrapper. + JSCompartment* origin = js::GetObjectCompartment(obj); + JSCompartment* target = js::GetContextCompartment(cx); + bool originIsChrome = AccessCheck::isChrome(origin); + bool targetIsChrome = AccessCheck::isChrome(target); + bool originSubsumesTarget = AccessCheck::subsumesConsideringDomain(origin, target); + bool targetSubsumesOrigin = AccessCheck::subsumesConsideringDomain(target, origin); + bool sameOrigin = targetSubsumesOrigin && originSubsumesTarget; + XrayType xrayType = GetXrayType(obj); + + const Wrapper* wrapper; + + // + // First, handle the special cases. + // + + // If UniversalXPConnect is enabled, this is just some dumb mochitest. Use + // a vanilla CCW. + if (xpc::IsUniversalXPConnectEnabled(target)) { + CrashIfNotInAutomation(); + wrapper = &CrossCompartmentWrapper::singleton; + } + + // Let the SpecialPowers scope make its stuff easily accessible to content. + else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) { + CrashIfNotInAutomation(); + wrapper = &CrossCompartmentWrapper::singleton; + } + + // Special handling for chrome objects being exposed to content. + else if (originIsChrome && !targetIsChrome) { + // If this is a chrome function being exposed to content, we need to allow + // call (but nothing else). We allow CPOWs that purport to be function's + // here, but only in the content process. + if ((IdentifyStandardInstance(obj) == JSProto_Function || + (jsipc::IsCPOW(obj) && JS::IsCallable(obj) && + XRE_IsContentProcess()))) + { + wrapper = &FilteringWrapper::singleton; + } + + // For Vanilla JSObjects exposed from chrome to content, we use a wrapper + // that supports __exposedProps__. We'd like to get rid of these eventually, + // but in their current form they don't cause much trouble. + else if (IdentifyStandardInstance(obj) == JSProto_Object) { + wrapper = &ChromeObjectWrapper::singleton; + } + + // Otherwise we get an opaque wrapper. + else { + wrapper = &FilteringWrapper::singleton; + } + } + + // + // Now, handle the regular cases. + // + // These are wrappers we can compute using a rule-based approach. In order + // to do so, we need to compute some parameters. + // + else { + + // The wrapper is a security wrapper (protecting the wrappee) if and + // only if the target does not subsume the origin. + bool securityWrapper = !targetSubsumesOrigin; + + // Xrays are warranted if either the target or the origin don't trust + // each other. This is generally the case, unless the two are same-origin + // and the caller has not requested same-origin Xrays. + // + // Xrays are a bidirectional protection, since it affords clarity to the + // caller and privacy to the callee. + bool sameOriginXrays = CompartmentPrivate::Get(origin)->wantXrays || + CompartmentPrivate::Get(target)->wantXrays; + bool wantXrays = !sameOrigin || sameOriginXrays; + + // If Xrays are warranted, the caller may waive them for non-security + // wrappers (unless explicitly forbidden from doing so). + bool waiveXrays = wantXrays && !securityWrapper && + CompartmentPrivate::Get(target)->allowWaivers && + HasWaiveXrayFlag(obj); + + // We have slightly different behavior for the case when the object + // being wrapped is in an XBL scope. + bool originIsContentXBLScope = IsContentXBLScope(origin); + + wrapper = SelectWrapper(securityWrapper, wantXrays, xrayType, waiveXrays, + originIsContentXBLScope, obj); + + // If we want to apply add-on interposition in the target compartment, + // then we try to "upgrade" the wrapper to an interposing one. + if (CompartmentPrivate::Get(target)->scope->HasInterposition()) + wrapper = SelectAddonWrapper(cx, obj, wrapper); + } + + if (!targetSubsumesOrigin) { + // Do a belt-and-suspenders check against exposing eval()/Function() to + // non-subsuming content. + if (JSFunction* fun = JS_GetObjectFunction(obj)) { + if (JS_IsBuiltinEvalFunction(fun) || JS_IsBuiltinFunctionConstructor(fun)) { + NS_WARNING("Trying to expose eval or Function to non-subsuming content!"); + wrapper = &FilteringWrapper::singleton; + } + } + } + + DEBUG_CheckUnwrapSafety(obj, wrapper, origin, target); + + if (existing) + return Wrapper::Renew(cx, existing, obj, wrapper); + + return Wrapper::New(cx, obj, wrapper); +} + +// Call WaiveXrayAndWrap when you have a JS object that you don't want to be +// wrapped in an Xray wrapper. cx->compartment is the compartment that will be +// using the returned object. If the object to be wrapped is already in the +// correct compartment, then this returns the unwrapped object. +bool +WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleValue vp) +{ + if (vp.isPrimitive()) + return JS_WrapValue(cx, vp); + + RootedObject obj(cx, &vp.toObject()); + if (!WaiveXrayAndWrap(cx, &obj)) + return false; + + vp.setObject(*obj); + return true; +} + +bool +WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleObject argObj) +{ + MOZ_ASSERT(argObj); + RootedObject obj(cx, js::UncheckedUnwrap(argObj)); + MOZ_ASSERT(!js::IsWindow(obj)); + if (js::IsObjectInContextCompartment(obj, cx)) { + argObj.set(obj); + return true; + } + + // Even though waivers have no effect on access by scopes that don't subsume + // the underlying object, good defense-in-depth dictates that we should avoid + // handing out waivers to callers that can't use them. The transitive waiving + // machinery unconditionally calls WaiveXrayAndWrap on return values from + // waived functions, even though the return value might be not be same-origin + // with the function. So if we find ourselves trying to create a waiver for + // |cx|, we should check whether the caller has any business with waivers + // to things in |obj|'s compartment. + JSCompartment* target = js::GetContextCompartment(cx); + JSCompartment* origin = js::GetObjectCompartment(obj); + obj = AllowWaiver(target, origin) ? WaiveXray(cx, obj) : obj; + if (!obj) + return false; + + if (!JS_WrapObject(cx, &obj)) + return false; + argObj.set(obj); + return true; +} + +/* + * Calls to JS_TransplantObject* should go through these helpers here so that + * waivers get fixed up properly. + */ + +static bool +FixWaiverAfterTransplant(JSContext* cx, HandleObject oldWaiver, HandleObject newobj) +{ + MOZ_ASSERT(Wrapper::wrapperHandler(oldWaiver) == &XrayWaiver); + MOZ_ASSERT(!js::IsCrossCompartmentWrapper(newobj)); + + // Create a waiver in the new compartment. We know there's not one already + // because we _just_ transplanted, which means that |newobj| was either + // created from scratch, or was previously cross-compartment wrapper (which + // should have no waiver). CreateXrayWaiver asserts this. + JSObject* newWaiver = WrapperFactory::CreateXrayWaiver(cx, newobj); + if (!newWaiver) + return false; + + // Update all the cross-compartment references to oldWaiver to point to + // newWaiver. + if (!js::RemapAllWrappersForObject(cx, oldWaiver, newWaiver)) + return false; + + // There should be no same-compartment references to oldWaiver, and we + // just remapped all cross-compartment references. It's dead, so we can + // remove it from the map. + XPCWrappedNativeScope* scope = ObjectScope(oldWaiver); + JSObject* key = Wrapper::wrappedObject(oldWaiver); + MOZ_ASSERT(scope->mWaiverWrapperMap->Find(key)); + scope->mWaiverWrapperMap->Remove(key); + return true; +} + +JSObject* +TransplantObject(JSContext* cx, JS::HandleObject origobj, JS::HandleObject target) +{ + RootedObject oldWaiver(cx, WrapperFactory::GetXrayWaiver(origobj)); + RootedObject newIdentity(cx, JS_TransplantObject(cx, origobj, target)); + if (!newIdentity || !oldWaiver) + return newIdentity; + + if (!FixWaiverAfterTransplant(cx, oldWaiver, newIdentity)) + return nullptr; + return newIdentity; +} + +nsIGlobalObject* +NativeGlobal(JSObject* obj) +{ + obj = js::GetGlobalForObjectCrossCompartment(obj); + + // Every global needs to hold a native as its private or be a + // WebIDL object with an nsISupports DOM object. + MOZ_ASSERT((GetObjectClass(obj)->flags & (JSCLASS_PRIVATE_IS_NSISUPPORTS | + JSCLASS_HAS_PRIVATE)) || + dom::UnwrapDOMObjectToISupports(obj)); + + nsISupports* native = dom::UnwrapDOMObjectToISupports(obj); + if (!native) { + native = static_cast(js::GetObjectPrivate(obj)); + MOZ_ASSERT(native); + + // In some cases (like for windows) it is a wrapped native, + // in other cases (sandboxes, backstage passes) it's just + // a direct pointer to the native. If it's a wrapped native + // let's unwrap it first. + if (nsCOMPtr wn = do_QueryInterface(native)) { + native = wn->Native(); + } + } + + nsCOMPtr global = do_QueryInterface(native); + MOZ_ASSERT(global, "Native held by global needs to implement nsIGlobalObject!"); + + return global; +} + +} // namespace xpc diff --git a/js/xpconnect/wrappers/WrapperFactory.h b/js/xpconnect/wrappers/WrapperFactory.h new file mode 100644 index 000000000..122267830 --- /dev/null +++ b/js/xpconnect/wrappers/WrapperFactory.h @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef _xpc_WRAPPERFACTORY_H +#define _xpc_WRAPPERFACTORY_H + +#include "jswrapper.h" + +namespace xpc { + +class WrapperFactory { + public: + enum { WAIVE_XRAY_WRAPPER_FLAG = js::Wrapper::LAST_USED_FLAG << 1, + IS_XRAY_WRAPPER_FLAG = WAIVE_XRAY_WRAPPER_FLAG << 1 }; + + // Return true if any of any of the nested wrappers have the flag set. + static bool HasWrapperFlag(JSObject* wrapper, unsigned flag) { + unsigned flags = 0; + js::UncheckedUnwrap(wrapper, true, &flags); + return !!(flags & flag); + } + + static bool IsXrayWrapper(JSObject* wrapper) { + return HasWrapperFlag(wrapper, IS_XRAY_WRAPPER_FLAG); + } + + static bool HasWaiveXrayFlag(JSObject* wrapper) { + return HasWrapperFlag(wrapper, WAIVE_XRAY_WRAPPER_FLAG); + } + + static bool IsCOW(JSObject* wrapper); + + static JSObject* GetXrayWaiver(JS::HandleObject obj); + static JSObject* CreateXrayWaiver(JSContext* cx, JS::HandleObject obj); + static JSObject* WaiveXray(JSContext* cx, JSObject* obj); + + // Computes whether we should allow the creation of an Xray waiver from + // |target| to |origin|. + static bool AllowWaiver(JSCompartment* target, JSCompartment* origin); + + // Convenience method for the above, operating on a wrapper. + static bool AllowWaiver(JSObject* wrapper); + + // Prepare a given object for wrapping in a new compartment. + static void PrepareForWrapping(JSContext* cx, + JS::HandleObject scope, + JS::HandleObject obj, + JS::HandleObject objectPassedToWrap, + JS::MutableHandleObject retObj); + + // Rewrap an object that is about to cross compartment boundaries. + static JSObject* Rewrap(JSContext* cx, + JS::HandleObject existing, + JS::HandleObject obj); + + // Wrap wrapped object into a waiver wrapper and then re-wrap it. + static bool WaiveXrayAndWrap(JSContext* cx, JS::MutableHandleValue vp); + static bool WaiveXrayAndWrap(JSContext* cx, JS::MutableHandleObject object); +}; + +extern const js::Wrapper XrayWaiver; + +} // namespace xpc + +#endif /* _xpc_WRAPPERFACTORY_H */ diff --git a/js/xpconnect/wrappers/XrayWrapper.cpp b/js/xpconnect/wrappers/XrayWrapper.cpp new file mode 100644 index 000000000..5e537692d --- /dev/null +++ b/js/xpconnect/wrappers/XrayWrapper.cpp @@ -0,0 +1,2466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "XrayWrapper.h" +#include "AccessCheck.h" +#include "WrapperFactory.h" + +#include "nsDependentString.h" +#include "nsIScriptError.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "XPCWrapper.h" +#include "xpcprivate.h" + +#include "jsapi.h" +#include "jsprf.h" +#include "nsJSUtils.h" +#include "nsPrintfCString.h" + +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/dom/WindowBinding.h" +#include "mozilla/dom/XrayExpandoClass.h" +#include "nsGlobalWindow.h" + +using namespace mozilla::dom; +using namespace JS; +using namespace mozilla; + +using js::Wrapper; +using js::BaseProxyHandler; +using js::IsCrossCompartmentWrapper; +using js::UncheckedUnwrap; +using js::CheckedUnwrap; + +namespace xpc { + +using namespace XrayUtils; + +#define Between(x, a, b) (a <= x && x <= b) + +static_assert(JSProto_URIError - JSProto_Error == 7, "New prototype added in error object range"); +#define AssertErrorObjectKeyInBounds(key) \ + static_assert(Between(key, JSProto_Error, JSProto_URIError), "We depend on jsprototypes.h ordering here"); +MOZ_FOR_EACH(AssertErrorObjectKeyInBounds, (), + (JSProto_Error, JSProto_InternalError, JSProto_EvalError, JSProto_RangeError, + JSProto_ReferenceError, JSProto_SyntaxError, JSProto_TypeError, JSProto_URIError)); + +static_assert(JSProto_Uint8ClampedArray - JSProto_Int8Array == 8, "New prototype added in typed array range"); +#define AssertTypedArrayKeyInBounds(key) \ + static_assert(Between(key, JSProto_Int8Array, JSProto_Uint8ClampedArray), "We depend on jsprototypes.h ordering here"); +MOZ_FOR_EACH(AssertTypedArrayKeyInBounds, (), + (JSProto_Int8Array, JSProto_Uint8Array, JSProto_Int16Array, JSProto_Uint16Array, + JSProto_Int32Array, JSProto_Uint32Array, JSProto_Float32Array, JSProto_Float64Array, JSProto_Uint8ClampedArray)); + +#undef Between + +inline bool +IsErrorObjectKey(JSProtoKey key) +{ + return key >= JSProto_Error && key <= JSProto_URIError; +} + +inline bool +IsTypedArrayKey(JSProtoKey key) +{ + return key >= JSProto_Int8Array && key <= JSProto_Uint8ClampedArray; +} + +// Whitelist for the standard ES classes we can Xray to. +static bool +IsJSXraySupported(JSProtoKey key) +{ + if (IsTypedArrayKey(key)) + return true; + if (IsErrorObjectKey(key)) + return true; + switch (key) { + case JSProto_Date: + case JSProto_Object: + case JSProto_Array: + case JSProto_Function: + case JSProto_TypedArray: + case JSProto_SavedFrame: + case JSProto_RegExp: + case JSProto_Promise: + case JSProto_ArrayBuffer: + case JSProto_SharedArrayBuffer: + return true; + default: + return false; + } +} + +XrayType +GetXrayType(JSObject* obj) +{ + obj = js::UncheckedUnwrap(obj, /* stopAtWindowProxy = */ false); + if (mozilla::dom::UseDOMXray(obj)) + return XrayForDOMObject; + + const js::Class* clasp = js::GetObjectClass(obj); + if (IS_WN_CLASS(clasp) || js::IsWindowProxy(obj)) + return XrayForWrappedNative; + + JSProtoKey standardProto = IdentifyStandardInstanceOrPrototype(obj); + if (IsJSXraySupported(standardProto)) + return XrayForJSObject; + + // Modulo a few exceptions, everything else counts as an XrayWrapper to an + // opaque object, which means that more-privileged code sees nothing from + // the underlying object. This is very important for security. In some cases + // though, we need to make an exception for compatibility. + if (IsSandbox(obj)) + return NotXray; + + return XrayForOpaqueObject; +} + +JSObject* +XrayAwareCalleeGlobal(JSObject* fun) +{ + MOZ_ASSERT(js::IsFunctionObject(fun)); + + if (!js::FunctionHasNativeReserved(fun)) { + // Just a normal function, no Xrays involved. + return js::GetGlobalForObjectCrossCompartment(fun); + } + + // The functions we expect here have the Xray wrapper they're associated with + // in their XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT and, in a debug build, + // themselves in their XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF. Assert that + // last bit. + MOZ_ASSERT(&js::GetFunctionNativeReserved(fun, XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF).toObject() == + fun); + + Value v = + js::GetFunctionNativeReserved(fun, XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT); + MOZ_ASSERT(IsXrayWrapper(&v.toObject())); + + JSObject* xrayTarget = js::UncheckedUnwrap(&v.toObject()); + return js::GetGlobalForObjectCrossCompartment(xrayTarget); +} + +JSObject* +XrayTraits::getExpandoChain(HandleObject obj) +{ + return ObjectScope(obj)->GetExpandoChain(obj); +} + +bool +XrayTraits::setExpandoChain(JSContext* cx, HandleObject obj, HandleObject chain) +{ + return ObjectScope(obj)->SetExpandoChain(cx, obj, chain); +} + +// static +XPCWrappedNative* +XPCWrappedNativeXrayTraits::getWN(JSObject* wrapper) +{ + return XPCWrappedNative::Get(getTargetObject(wrapper)); +} + +const JSClass XPCWrappedNativeXrayTraits::HolderClass = { + "NativePropertyHolder", JSCLASS_HAS_RESERVED_SLOTS(2) +}; + + +const JSClass JSXrayTraits::HolderClass = { + "JSXrayHolder", JSCLASS_HAS_RESERVED_SLOTS(SLOT_COUNT) +}; + +bool +OpaqueXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, HandleObject wrapper, + HandleObject holder, HandleId id, + MutableHandle desc) +{ + bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, id, desc); + if (!ok || desc.object()) + return ok; + + return ReportWrapperDenial(cx, id, WrapperDenialForXray, "object is not safely Xrayable"); +} + +bool +ReportWrapperDenial(JSContext* cx, HandleId id, WrapperDenialType type, const char* reason) +{ + CompartmentPrivate* priv = CompartmentPrivate::Get(CurrentGlobalOrNull(cx)); + bool alreadyWarnedOnce = priv->wrapperDenialWarnings[type]; + priv->wrapperDenialWarnings[type] = true; + + // The browser console warning is only emitted for the first violation, + // whereas the (debug-only) NS_WARNING is emitted for each violation. +#ifndef DEBUG + if (alreadyWarnedOnce) + return true; +#endif + + nsAutoJSString propertyName; + RootedValue idval(cx); + if (!JS_IdToValue(cx, id, &idval)) + return false; + JSString* str = JS_ValueToSource(cx, idval); + if (!str) + return false; + if (!propertyName.init(cx, str)) + return false; + AutoFilename filename; + unsigned line = 0, column = 0; + DescribeScriptedCaller(cx, &filename, &line, &column); + + // Warn to the terminal for the logs. + NS_WARNING(nsPrintfCString("Silently denied access to property %s: %s (@%s:%u:%u)", + NS_LossyConvertUTF16toASCII(propertyName).get(), reason, + filename.get(), line, column).get()); + + // If this isn't the first warning on this topic for this global, we've + // already bailed out in opt builds. Now that the NS_WARNING is done, bail + // out in debug builds as well. + if (alreadyWarnedOnce) + return true; + + // + // Log a message to the console service. + // + + // Grab the pieces. + nsCOMPtr consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID); + NS_ENSURE_TRUE(consoleService, true); + nsCOMPtr errorObject = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + NS_ENSURE_TRUE(errorObject, true); + + // Compute the current window id if any. + uint64_t windowId = 0; + nsGlobalWindow* win = WindowGlobalOrNull(CurrentGlobalOrNull(cx)); + if (win) + windowId = win->WindowID(); + + + Maybe errorMessage; + if (type == WrapperDenialForXray) { + errorMessage.emplace("XrayWrapper denied access to property %s (reason: %s). " + "See https://developer.mozilla.org/en-US/docs/Xray_vision " + "for more information. Note that only the first denied " + "property access from a given global object will be reported.", + NS_LossyConvertUTF16toASCII(propertyName).get(), + reason); + } else { + MOZ_ASSERT(type == WrapperDenialForCOW); + errorMessage.emplace("Security wrapper denied access to property %s on privileged " + "Javascript object. Support for exposing privileged objects " + "to untrusted content via __exposedProps__ is being gradually " + "removed - use WebIDL bindings or Components.utils.cloneInto " + "instead. Note that only the first denied property access from a " + "given global object will be reported.", + NS_LossyConvertUTF16toASCII(propertyName).get()); + } + nsString filenameStr(NS_ConvertASCIItoUTF16(filename.get())); + nsresult rv = errorObject->InitWithWindowID(NS_ConvertASCIItoUTF16(errorMessage.ref()), + filenameStr, + EmptyString(), + line, column, + nsIScriptError::warningFlag, + "XPConnect", + windowId); + NS_ENSURE_SUCCESS(rv, true); + rv = consoleService->LogMessage(errorObject); + NS_ENSURE_SUCCESS(rv, true); + + return true; +} + +bool JSXrayTraits::getOwnPropertyFromWrapperIfSafe(JSContext* cx, + HandleObject wrapper, + HandleId id, + MutableHandle outDesc) +{ + MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); + RootedObject target(cx, getTargetObject(wrapper)); + { + JSAutoCompartment ac(cx, target); + if (!getOwnPropertyFromTargetIfSafe(cx, target, wrapper, id, outDesc)) + return false; + } + return JS_WrapPropertyDescriptor(cx, outDesc); +} + +bool JSXrayTraits::getOwnPropertyFromTargetIfSafe(JSContext* cx, + HandleObject target, + HandleObject wrapper, + HandleId id, + MutableHandle outDesc) +{ + // Note - This function operates in the target compartment, because it + // avoids a bunch of back-and-forth wrapping in enumerateNames. + MOZ_ASSERT(getTargetObject(wrapper) == target); + MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); + MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper)); + MOZ_ASSERT(outDesc.object() == nullptr); + + Rooted desc(cx); + if (!JS_GetOwnPropertyDescriptorById(cx, target, id, &desc)) + return false; + + // If the property doesn't exist at all, we're done. + if (!desc.object()) + return true; + + // Disallow accessor properties. + if (desc.hasGetterOrSetter()) { + JSAutoCompartment ac(cx, wrapper); + return ReportWrapperDenial(cx, id, WrapperDenialForXray, "property has accessor"); + } + + // Apply extra scrutiny to objects. + if (desc.value().isObject()) { + RootedObject propObj(cx, js::UncheckedUnwrap(&desc.value().toObject())); + JSAutoCompartment ac(cx, propObj); + + // Disallow non-subsumed objects. + if (!AccessCheck::subsumes(target, propObj)) { + JSAutoCompartment ac(cx, wrapper); + return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not same-origin with target"); + } + + // Disallow non-Xrayable objects. + XrayType xrayType = GetXrayType(propObj); + if (xrayType == NotXray || xrayType == XrayForOpaqueObject) { + JSAutoCompartment ac(cx, wrapper); + return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value not Xrayable"); + } + + // Disallow callables. + if (JS::IsCallable(propObj)) { + JSAutoCompartment ac(cx, wrapper); + return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value is callable"); + } + } + + // Disallow any property that shadows something on its (Xrayed) + // prototype chain. + JSAutoCompartment ac2(cx, wrapper); + RootedObject proto(cx); + bool foundOnProto = false; + if (!JS_GetPrototype(cx, wrapper, &proto) || + (proto && !JS_HasPropertyById(cx, proto, id, &foundOnProto))) + { + return false; + } + if (foundOnProto) + return ReportWrapperDenial(cx, id, WrapperDenialForXray, "value shadows a property on the standard prototype"); + + // We made it! Assign over the descriptor, and don't forget to wrap. + outDesc.assign(desc.get()); + return true; +} + +// Returns true on success (in the JSAPI sense), false on failure. If true is +// returned, desc.object() will indicate whether we actually resolved +// the property. +// +// id is the property id we're looking for. +// holder is the object to define the property on. +// fs is the relevant JSFunctionSpec*. +// ps is the relevant JSPropertySpec*. +// desc is the descriptor we're resolving into. +static bool +TryResolvePropertyFromSpecs(JSContext* cx, HandleId id, HandleObject holder, + const JSFunctionSpec* fs, + const JSPropertySpec* ps, + MutableHandle desc) +{ + // Scan through the functions. + const JSFunctionSpec* fsMatch = nullptr; + for ( ; fs && fs->name; ++fs) { + if (PropertySpecNameEqualsId(fs->name, id)) { + fsMatch = fs; + break; + } + } + if (fsMatch) { + // Generate an Xrayed version of the method. + RootedFunction fun(cx, JS::NewFunctionFromSpec(cx, fsMatch, id)); + if (!fun) + return false; + + // The generic Xray machinery only defines non-own properties of the target on + // the holder. This is broken, and will be fixed at some point, but for now we + // need to cache the value explicitly. See the corresponding call to + // JS_GetOwnPropertyDescriptorById at the top of + // JSXrayTraits::resolveOwnProperty. + RootedObject funObj(cx, JS_GetFunctionObject(fun)); + return JS_DefinePropertyById(cx, holder, id, funObj, 0) && + JS_GetOwnPropertyDescriptorById(cx, holder, id, desc); + } + + // Scan through the properties. + const JSPropertySpec* psMatch = nullptr; + for ( ; ps && ps->name; ++ps) { + if (PropertySpecNameEqualsId(ps->name, id)) { + psMatch = ps; + break; + } + } + if (psMatch) { + desc.value().setUndefined(); + RootedFunction getterObj(cx); + RootedFunction setterObj(cx); + unsigned flags = psMatch->flags; + if (psMatch->isAccessor()) { + if (psMatch->isSelfHosted()) { + getterObj = JS::GetSelfHostedFunction(cx, psMatch->accessors.getter.selfHosted.funname, id, 0); + if (!getterObj) + return false; + desc.setGetterObject(JS_GetFunctionObject(getterObj)); + if (psMatch->accessors.setter.selfHosted.funname) { + MOZ_ASSERT(flags & JSPROP_SETTER); + setterObj = JS::GetSelfHostedFunction(cx, psMatch->accessors.setter.selfHosted.funname, id, 0); + if (!setterObj) + return false; + desc.setSetterObject(JS_GetFunctionObject(setterObj)); + } + } else { + desc.setGetter(JS_CAST_NATIVE_TO(psMatch->accessors.getter.native.op, + JSGetterOp)); + desc.setSetter(JS_CAST_NATIVE_TO(psMatch->accessors.setter.native.op, + JSSetterOp)); + } + desc.setAttributes(flags); + } else { + RootedValue v(cx); + if (!psMatch->getValue(cx, &v)) + return false; + desc.value().set(v); + desc.setAttributes(flags & ~JSPROP_INTERNAL_USE_BIT); + } + + // The generic Xray machinery only defines non-own properties on the holder. + // This is broken, and will be fixed at some point, but for now we need to + // cache the value explicitly. See the corresponding call to + // JS_GetPropertyById at the top of JSXrayTraits::resolveOwnProperty. + // + // Note also that the public-facing API here doesn't give us a way to + // pass along JITInfo. It's probably ok though, since Xrays are already + // pretty slow. + return JS_DefinePropertyById(cx, holder, id, + desc.value(), + // This particular descriptor, unlike most, + // actually stores JSNatives directly, + // since we just set it up. Do NOT pass + // JSPROP_PROPOP_ACCESSORS here! + desc.attributes(), + JS_PROPERTYOP_GETTER(desc.getter()), + JS_PROPERTYOP_SETTER(desc.setter())) && + JS_GetOwnPropertyDescriptorById(cx, holder, id, desc); + } + + return true; +} + +static bool +ShouldResolveStaticProperties(JSProtoKey key) +{ + // Don't try to resolve static properties on RegExp, because they + // have issues. In particular, some of them grab state off the + // global of the RegExp constructor that describes the last regexp + // evaluation in that global, which is not a useful thing to do + // over Xrays. + return key != JSProto_RegExp; +} + +bool +JSXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, + HandleObject wrapper, HandleObject holder, + HandleId id, + MutableHandle desc) +{ + // Call the common code. + bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, + id, desc); + if (!ok || desc.object()) + return ok; + + // The non-HasPrototypes semantics implemented by traditional Xrays are kind + // of broken with respect to |own|-ness and the holder. The common code + // muddles through by only checking the holder for non-|own| lookups, but + // that doesn't work for us. So we do an explicit holder check here, and hope + // that this mess gets fixed up soon. + if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) + return false; + if (desc.object()) { + desc.object().set(wrapper); + return true; + } + + RootedObject target(cx, getTargetObject(wrapper)); + JSProtoKey key = getProtoKey(holder); + if (!isPrototype(holder)) { + // For Object and Array instances, we expose some properties from the + // underlying object, but only after filtering them carefully. + // + // Note that, as far as JS observables go, Arrays are just Objects with + // a different prototype and a magic (own, non-configurable) |.length| that + // serves as a non-tight upper bound on |own| indexed properties. So while + // it's tempting to try to impose some sort of structure on what Arrays + // "should" look like over Xrays, the underlying object is squishy enough + // that it makes sense to just treat them like Objects for Xray purposes. + if (key == JSProto_Object || key == JSProto_Array) { + return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc); + } else if (IsTypedArrayKey(key)) { + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + // WebExtensions can't use cloneInto(), so we just let them do + // the slow thing to maximize compatibility. + if (CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->isWebExtensionContentScript) { + Rooted innerDesc(cx); + { + JSAutoCompartment ac(cx, target); + if (!JS_GetOwnPropertyDescriptorById(cx, target, id, &innerDesc)) + return false; + } + if (innerDesc.isDataDescriptor() && innerDesc.value().isNumber()) { + desc.setValue(innerDesc.value()); + desc.object().set(wrapper); + } + return true; + } else { + JS_ReportErrorASCII(cx, "Accessing TypedArray data over Xrays is slow, and forbidden " + "in order to encourage performant code. To copy TypedArrays " + "across origin boundaries, consider using Components.utils.cloneInto()."); + return false; + } + } + } else if (key == JSProto_Function) { + if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_LENGTH)) { + FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY, + NumberValue(JS_GetFunctionArity(JS_GetObjectFunction(target)))); + return true; + } else if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAME)) { + RootedString fname(cx, JS_GetFunctionId(JS_GetObjectFunction(target))); + FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY, + fname ? StringValue(fname) : JS_GetEmptyStringValue(cx)); + } else { + // Look for various static properties/methods and the + // 'prototype' property. + JSProtoKey standardConstructor = constructorFor(holder); + if (standardConstructor != JSProto_Null) { + // Handle the 'prototype' property to make + // xrayedGlobal.StandardClass.prototype work. + if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_PROTOTYPE)) { + RootedObject standardProto(cx); + { + JSAutoCompartment ac(cx, target); + if (!JS_GetClassPrototype(cx, standardConstructor, &standardProto)) + return false; + MOZ_ASSERT(standardProto); + } + + if (!JS_WrapObject(cx, &standardProto)) + return false; + FillPropertyDescriptor(desc, wrapper, JSPROP_PERMANENT | JSPROP_READONLY, + ObjectValue(*standardProto)); + return true; + } + + if (ShouldResolveStaticProperties(standardConstructor)) { + const js::Class* clasp = js::ProtoKeyToClass(standardConstructor); + MOZ_ASSERT(clasp->specDefined()); + + if (!TryResolvePropertyFromSpecs(cx, id, holder, + clasp->specConstructorFunctions(), + clasp->specConstructorProperties(), desc)) { + return false; + } + + if (desc.object()) { + desc.object().set(wrapper); + return true; + } + } + } + } + } else if (IsErrorObjectKey(key)) { + // The useful state of error objects (except for .stack) is + // (unfortunately) represented as own data properties per-spec. This + // means that we can't have a a clean representation of the data + // (free from tampering) without doubling the slots of Error + // objects, which isn't great. So we forward these properties to the + // underlying object and then just censor any values with the wrong + // type. This limits the ability of content to do anything all that + // confusing. + bool isErrorIntProperty = + id == GetJSIDByIndex(cx, XPCJSContext::IDX_LINENUMBER) || + id == GetJSIDByIndex(cx, XPCJSContext::IDX_COLUMNNUMBER); + bool isErrorStringProperty = + id == GetJSIDByIndex(cx, XPCJSContext::IDX_FILENAME) || + id == GetJSIDByIndex(cx, XPCJSContext::IDX_MESSAGE); + if (isErrorIntProperty || isErrorStringProperty) { + RootedObject waiver(cx, wrapper); + if (!WrapperFactory::WaiveXrayAndWrap(cx, &waiver)) + return false; + if (!JS_GetOwnPropertyDescriptorById(cx, waiver, id, desc)) + return false; + bool valueMatchesType = (isErrorIntProperty && desc.value().isInt32()) || + (isErrorStringProperty && desc.value().isString()); + if (desc.hasGetterOrSetter() || !valueMatchesType) + FillPropertyDescriptor(desc, nullptr, 0, UndefinedValue()); + return true; + } + } else if (key == JSProto_RegExp) { + if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX)) + return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc); + } + + // The rest of this function applies only to prototypes. + return true; + } + + // Handle the 'constructor' property. + if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_CONSTRUCTOR)) { + RootedObject constructor(cx); + { + JSAutoCompartment ac(cx, target); + if (!JS_GetClassObject(cx, key, &constructor)) + return false; + } + if (!JS_WrapObject(cx, &constructor)) + return false; + desc.object().set(wrapper); + desc.setAttributes(0); + desc.setGetter(nullptr); + desc.setSetter(nullptr); + desc.value().setObject(*constructor); + return true; + } + + // Handle the 'name' property for error prototypes. + if (IsErrorObjectKey(key) && id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAME)) { + RootedId className(cx); + ProtoKeyToId(cx, key, &className); + FillPropertyDescriptor(desc, wrapper, 0, UndefinedValue()); + return JS_IdToValue(cx, className, desc.value()); + } + + // Handle the 'lastIndex' property for RegExp prototypes. + if (key == JSProto_RegExp && id == GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX)) + return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc); + + // Grab the JSClass. We require all Xrayable classes to have a ClassSpec. + const js::Class* clasp = js::GetObjectClass(target); + MOZ_ASSERT(clasp->specDefined()); + + // Indexed array properties are handled above, so we can just work with the + // class spec here. + if (!TryResolvePropertyFromSpecs(cx, id, holder, + clasp->specPrototypeFunctions(), + clasp->specPrototypeProperties(), + desc)) { + return false; + } + + if (desc.object()) { + desc.object().set(wrapper); + } + + return true; +} + +bool +JSXrayTraits::delete_(JSContext* cx, HandleObject wrapper, HandleId id, ObjectOpResult& result) +{ + RootedObject holder(cx, ensureHolder(cx, wrapper)); + + // If we're using Object Xrays, we allow callers to attempt to delete any + // property from the underlying object that they are able to resolve. Note + // that this deleting may fail if the property is non-configurable. + JSProtoKey key = getProtoKey(holder); + bool isObjectOrArrayInstance = (key == JSProto_Object || key == JSProto_Array) && + !isPrototype(holder); + if (isObjectOrArrayInstance) { + RootedObject target(cx, getTargetObject(wrapper)); + JSAutoCompartment ac(cx, target); + Rooted desc(cx); + if (!getOwnPropertyFromTargetIfSafe(cx, target, wrapper, id, &desc)) + return false; + if (desc.object()) + return JS_DeletePropertyById(cx, target, id, result); + } + return result.succeed(); +} + +bool +JSXrayTraits::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id, + Handle desc, + Handle existingDesc, + ObjectOpResult& result, + bool* defined) +{ + *defined = false; + RootedObject holder(cx, ensureHolder(cx, wrapper)); + if (!holder) + return false; + + + // Object and Array instances are special. For those cases, we forward property + // definitions to the underlying object if the following conditions are met: + // * The property being defined is a value-prop. + // * The property being defined is either a primitive or subsumed by the target. + // * As seen from the Xray, any existing property that we would overwrite is an + // |own| value-prop. + // + // To avoid confusion, we disallow expandos on Object and Array instances, and + // therefore raise an exception here if the above conditions aren't met. + JSProtoKey key = getProtoKey(holder); + bool isInstance = !isPrototype(holder); + bool isObjectOrArray = (key == JSProto_Object || key == JSProto_Array); + if (isObjectOrArray && isInstance) { + RootedObject target(cx, getTargetObject(wrapper)); + if (desc.hasGetterOrSetter()) { + JS_ReportErrorASCII(cx, "Not allowed to define accessor property on [Object] or [Array] XrayWrapper"); + return false; + } + if (desc.value().isObject() && + !AccessCheck::subsumes(target, js::UncheckedUnwrap(&desc.value().toObject()))) + { + JS_ReportErrorASCII(cx, "Not allowed to define cross-origin object as property on [Object] or [Array] XrayWrapper"); + return false; + } + if (existingDesc.hasGetterOrSetter()) { + JS_ReportErrorASCII(cx, "Not allowed to overwrite accessor property on [Object] or [Array] XrayWrapper"); + return false; + } + if (existingDesc.object() && existingDesc.object() != wrapper) { + JS_ReportErrorASCII(cx, "Not allowed to shadow non-own Xray-resolved property on [Object] or [Array] XrayWrapper"); + return false; + } + + Rooted wrappedDesc(cx, desc); + JSAutoCompartment ac(cx, target); + if (!JS_WrapPropertyDescriptor(cx, &wrappedDesc) || + !JS_DefinePropertyById(cx, target, id, wrappedDesc, result)) + { + return false; + } + *defined = true; + return true; + } + + // For WebExtensions content scripts, we forward the definition of indexed properties. By + // validating that the key and value are both numbers, we can avoid doing any wrapping. + if (isInstance && IsTypedArrayKey(key) && + CompartmentPrivate::Get(JS::CurrentGlobalOrNull(cx))->isWebExtensionContentScript && + desc.isDataDescriptor() && (desc.value().isNumber() || desc.value().isUndefined()) && + IsArrayIndex(GetArrayIndexFromId(cx, id))) + { + RootedObject target(cx, getTargetObject(wrapper)); + JSAutoCompartment ac(cx, target); + if (!JS_DefinePropertyById(cx, target, id, desc, result)) + return false; + *defined = true; + return true; + } + + return true; +} + +static bool +MaybeAppend(jsid id, unsigned flags, AutoIdVector& props) +{ + MOZ_ASSERT(!(flags & JSITER_SYMBOLSONLY)); + if (!(flags & JSITER_SYMBOLS) && JSID_IS_SYMBOL(id)) + return true; + return props.append(id); +} + +// Append the names from the given function and property specs to props. +static bool +AppendNamesFromFunctionAndPropertySpecs(JSContext* cx, + const JSFunctionSpec* fs, + const JSPropertySpec* ps, + unsigned flags, + AutoIdVector& props) +{ + // Convert the method and property names to jsids and pass them to the caller. + for ( ; fs && fs->name; ++fs) { + jsid id; + if (!PropertySpecNameToPermanentId(cx, fs->name, &id)) + return false; + if (!MaybeAppend(id, flags, props)) + return false; + } + for ( ; ps && ps->name; ++ps) { + jsid id; + if (!PropertySpecNameToPermanentId(cx, ps->name, &id)) + return false; + if (!MaybeAppend(id, flags, props)) + return false; + } + + return true; +} + +bool +JSXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags, + AutoIdVector& props) +{ + RootedObject target(cx, getTargetObject(wrapper)); + RootedObject holder(cx, ensureHolder(cx, wrapper)); + if (!holder) + return false; + + JSProtoKey key = getProtoKey(holder); + if (!isPrototype(holder)) { + // For Object and Array instances, we expose some properties from the underlying + // object, but only after filtering them carefully. + if (key == JSProto_Object || key == JSProto_Array) { + MOZ_ASSERT(props.empty()); + { + JSAutoCompartment ac(cx, target); + AutoIdVector targetProps(cx); + if (!js::GetPropertyKeys(cx, target, flags | JSITER_OWNONLY, &targetProps)) + return false; + // Loop over the properties, and only pass along the ones that + // we determine to be safe. + if (!props.reserve(targetProps.length())) + return false; + for (size_t i = 0; i < targetProps.length(); ++i) { + Rooted desc(cx); + RootedId id(cx, targetProps[i]); + if (!getOwnPropertyFromTargetIfSafe(cx, target, wrapper, id, &desc)) + return false; + if (desc.object()) + props.infallibleAppend(id); + } + } + return true; + } else if (IsTypedArrayKey(key)) { + uint32_t length = JS_GetTypedArrayLength(target); + // TypedArrays enumerate every indexed property in range, but + // |length| is a getter that lives on the proto, like it should be. + if (!props.reserve(length)) + return false; + for (int32_t i = 0; i <= int32_t(length - 1); ++i) + props.infallibleAppend(INT_TO_JSID(i)); + } else if (key == JSProto_Function) { + if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LENGTH))) + return false; + if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_NAME))) + return false; + // Handle the .prototype property and static properties on standard + // constructors. + JSProtoKey standardConstructor = constructorFor(holder); + if (standardConstructor != JSProto_Null) { + if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_PROTOTYPE))) + return false; + + if (ShouldResolveStaticProperties(standardConstructor)) { + const js::Class* clasp = js::ProtoKeyToClass(standardConstructor); + MOZ_ASSERT(clasp->specDefined()); + + if (!AppendNamesFromFunctionAndPropertySpecs( + cx, clasp->specConstructorFunctions(), + clasp->specConstructorProperties(), flags, props)) { + return false; + } + } + } + } else if (IsErrorObjectKey(key)) { + if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_FILENAME)) || + !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LINENUMBER)) || + !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_COLUMNNUMBER)) || + !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_STACK)) || + !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_MESSAGE))) + { + return false; + } + } else if (key == JSProto_RegExp) { + if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX))) + return false; + } + + // The rest of this function applies only to prototypes. + return true; + } + + // Add the 'constructor' property. + if (!props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_CONSTRUCTOR))) + return false; + + // For Error protoypes, add the 'name' property. + if (IsErrorObjectKey(key) && !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_NAME))) + return false; + + // For RegExp protoypes, add the 'lastIndex' property. + if (key == JSProto_RegExp && !props.append(GetJSIDByIndex(cx, XPCJSContext::IDX_LASTINDEX))) + return false; + + // Grab the JSClass. We require all Xrayable classes to have a ClassSpec. + const js::Class* clasp = js::GetObjectClass(target); + MOZ_ASSERT(clasp->specDefined()); + + return AppendNamesFromFunctionAndPropertySpecs( + cx, clasp->specPrototypeFunctions(), + clasp->specPrototypeProperties(), flags, props); +} + +bool +JSXrayTraits::construct(JSContext* cx, HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) +{ + JSXrayTraits& self = JSXrayTraits::singleton; + JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper)); + if (self.getProtoKey(holder) == JSProto_Function) { + JSProtoKey standardConstructor = constructorFor(holder); + if (standardConstructor == JSProto_Null) + return baseInstance.construct(cx, wrapper, args); + + const js::Class* clasp = js::ProtoKeyToClass(standardConstructor); + MOZ_ASSERT(clasp); + if (!(clasp->flags & JSCLASS_HAS_XRAYED_CONSTRUCTOR)) + return baseInstance.construct(cx, wrapper, args); + + // If the JSCLASS_HAS_XRAYED_CONSTRUCTOR flag is set on the Class, + // we don't use the constructor at hand. Instead, we retrieve the + // equivalent standard constructor in the xray compartment and run + // it in that compartment. The newTarget isn't unwrapped, and the + // constructor has to be able to detect and handle this situation. + // See the comments in js/public/Class.h and PromiseConstructor for + // details and an example. + RootedObject ctor(cx); + if (!JS_GetClassObject(cx, standardConstructor, &ctor)) + return false; + + RootedValue ctorVal(cx, ObjectValue(*ctor)); + HandleValueArray vals(args); + RootedObject result(cx); + if (!JS::Construct(cx, ctorVal, wrapper, vals, &result)) + return false; + AssertSameCompartment(cx, result); + args.rval().setObject(*result); + return true; + } + + JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; +} + +JSObject* +JSXrayTraits::createHolder(JSContext* cx, JSObject* wrapper) +{ + RootedObject target(cx, getTargetObject(wrapper)); + RootedObject holder(cx, JS_NewObjectWithGivenProto(cx, &HolderClass, + nullptr)); + if (!holder) + return nullptr; + + // Compute information about the target. + bool isPrototype = false; + JSProtoKey key = IdentifyStandardInstance(target); + if (key == JSProto_Null) { + isPrototype = true; + key = IdentifyStandardPrototype(target); + } + MOZ_ASSERT(key != JSProto_Null); + + // Store it on the holder. + RootedValue v(cx); + v.setNumber(static_cast(key)); + js::SetReservedSlot(holder, SLOT_PROTOKEY, v); + v.setBoolean(isPrototype); + js::SetReservedSlot(holder, SLOT_ISPROTOTYPE, v); + + // If this is a function, also compute whether it serves as a constructor + // for a standard class. + if (key == JSProto_Function) { + v.setNumber(static_cast(IdentifyStandardConstructor(target))); + js::SetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR, v); + } + + return holder; +} + +XPCWrappedNativeXrayTraits XPCWrappedNativeXrayTraits::singleton; +DOMXrayTraits DOMXrayTraits::singleton; +JSXrayTraits JSXrayTraits::singleton; +OpaqueXrayTraits OpaqueXrayTraits::singleton; + +XrayTraits* +GetXrayTraits(JSObject* obj) +{ + switch (GetXrayType(obj)) { + case XrayForDOMObject: + return &DOMXrayTraits::singleton; + case XrayForWrappedNative: + return &XPCWrappedNativeXrayTraits::singleton; + case XrayForJSObject: + return &JSXrayTraits::singleton; + case XrayForOpaqueObject: + return &OpaqueXrayTraits::singleton; + default: + return nullptr; + } +} + +/* + * Xray expando handling. + * + * We hang expandos for Xray wrappers off a reserved slot on the target object + * so that same-origin compartments can share expandos for a given object. We + * have a linked list of expando objects, one per origin. The properties on these + * objects are generally wrappers pointing back to the compartment that applied + * them. + * + * The expando objects should _never_ be exposed to script. The fact that they + * live in the target compartment is a detail of the implementation, and does + * not imply that code in the target compartment should be allowed to inspect + * them. They are private to the origin that placed them. + */ + +static nsIPrincipal* +ObjectPrincipal(JSObject* obj) +{ + return GetCompartmentPrincipal(js::GetObjectCompartment(obj)); +} + +static nsIPrincipal* +GetExpandoObjectPrincipal(JSObject* expandoObject) +{ + Value v = JS_GetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN); + return static_cast(v.toPrivate()); +} + +static void +ExpandoObjectFinalize(JSFreeOp* fop, JSObject* obj) +{ + // Release the principal. + nsIPrincipal* principal = GetExpandoObjectPrincipal(obj); + NS_RELEASE(principal); +} + +const JSClassOps XrayExpandoObjectClassOps = { + nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, ExpandoObjectFinalize +}; + +bool +XrayTraits::expandoObjectMatchesConsumer(JSContext* cx, + HandleObject expandoObject, + nsIPrincipal* consumerOrigin, + HandleObject exclusiveGlobal) +{ + MOZ_ASSERT(js::IsObjectInContextCompartment(expandoObject, cx)); + + // First, compare the principals. + nsIPrincipal* o = GetExpandoObjectPrincipal(expandoObject); + // Note that it's very important here to ignore document.domain. We + // pull the principal for the expando object off of the first consumer + // for a given origin, and freely share the expandos amongst multiple + // same-origin consumers afterwards. However, this means that we have + // no way to know whether _all_ consumers have opted in to collaboration + // by explicitly setting document.domain. So we just mandate that expando + // sharing is unaffected by it. + if (!consumerOrigin->Equals(o)) + return false; + + // Sandboxes want exclusive expando objects. + JSObject* owner = JS_GetReservedSlot(expandoObject, + JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL) + .toObjectOrNull(); + if (!owner && !exclusiveGlobal) + return true; + + // The exclusive global should always be wrapped in the target's compartment. + MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx)); + MOZ_ASSERT(!owner || js::IsObjectInContextCompartment(owner, cx)); + return owner == exclusiveGlobal; +} + +bool +XrayTraits::getExpandoObjectInternal(JSContext* cx, HandleObject target, + nsIPrincipal* origin, + JSObject* exclusiveGlobalArg, + MutableHandleObject expandoObject) +{ + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + expandoObject.set(nullptr); + + // The expando object lives in the compartment of the target, so all our + // work needs to happen there. + RootedObject exclusiveGlobal(cx, exclusiveGlobalArg); + JSAutoCompartment ac(cx, target); + if (!JS_WrapObject(cx, &exclusiveGlobal)) + return false; + + // Iterate through the chain, looking for a same-origin object. + RootedObject head(cx, getExpandoChain(target)); + while (head) { + if (expandoObjectMatchesConsumer(cx, head, origin, exclusiveGlobal)) { + expandoObject.set(head); + return true; + } + head = JS_GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull(); + } + + // Not found. + return true; +} + +bool +XrayTraits::getExpandoObject(JSContext* cx, HandleObject target, HandleObject consumer, + MutableHandleObject expandoObject) +{ + JSObject* consumerGlobal = js::GetGlobalForObjectCrossCompartment(consumer); + bool isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox"); + return getExpandoObjectInternal(cx, target, ObjectPrincipal(consumer), + isSandbox ? consumerGlobal : nullptr, + expandoObject); +} + +JSObject* +XrayTraits::attachExpandoObject(JSContext* cx, HandleObject target, + nsIPrincipal* origin, HandleObject exclusiveGlobal) +{ + // Make sure the compartments are sane. + MOZ_ASSERT(js::IsObjectInContextCompartment(target, cx)); + MOZ_ASSERT(!exclusiveGlobal || js::IsObjectInContextCompartment(exclusiveGlobal, cx)); + + // No duplicates allowed. +#ifdef DEBUG + { + RootedObject existingExpandoObject(cx); + if (getExpandoObjectInternal(cx, target, origin, exclusiveGlobal, &existingExpandoObject)) + MOZ_ASSERT(!existingExpandoObject); + else + JS_ClearPendingException(cx); + } +#endif + + // Create the expando object. + const JSClass* expandoClass = getExpandoClass(cx, target); + MOZ_ASSERT(!strcmp(expandoClass->name, "XrayExpandoObject")); + RootedObject expandoObject(cx, + JS_NewObjectWithGivenProto(cx, expandoClass, nullptr)); + if (!expandoObject) + return nullptr; + + // AddRef and store the principal. + NS_ADDREF(origin); + JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_ORIGIN, JS::PrivateValue(origin)); + + // Note the exclusive global, if any. + JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL, + ObjectOrNullValue(exclusiveGlobal)); + + // If this is our first expando object, take the opportunity to preserve + // the wrapper. This keeps our expandos alive even if the Xray wrapper gets + // collected. + RootedObject chain(cx, getExpandoChain(target)); + if (!chain) + preserveWrapper(target); + + // Insert it at the front of the chain. + JS_SetReservedSlot(expandoObject, JSSLOT_EXPANDO_NEXT, ObjectOrNullValue(chain)); + setExpandoChain(cx, target, expandoObject); + + return expandoObject; +} + +JSObject* +XrayTraits::ensureExpandoObject(JSContext* cx, HandleObject wrapper, + HandleObject target) +{ + // Expando objects live in the target compartment. + JSAutoCompartment ac(cx, target); + RootedObject expandoObject(cx); + if (!getExpandoObject(cx, target, wrapper, &expandoObject)) + return nullptr; + if (!expandoObject) { + // If the object is a sandbox, we don't want it to share expandos with + // anyone else, so we tag it with the sandbox global. + // + // NB: We first need to check the class, _then_ wrap for the target's + // compartment. + RootedObject consumerGlobal(cx, js::GetGlobalForObjectCrossCompartment(wrapper)); + bool isSandbox = !strcmp(js::GetObjectJSClass(consumerGlobal)->name, "Sandbox"); + if (!JS_WrapObject(cx, &consumerGlobal)) + return nullptr; + expandoObject = attachExpandoObject(cx, target, ObjectPrincipal(wrapper), + isSandbox ? (HandleObject)consumerGlobal : nullptr); + } + return expandoObject; +} + +bool +XrayTraits::cloneExpandoChain(JSContext* cx, HandleObject dst, HandleObject src) +{ + MOZ_ASSERT(js::IsObjectInContextCompartment(dst, cx)); + MOZ_ASSERT(getExpandoChain(dst) == nullptr); + + RootedObject oldHead(cx, getExpandoChain(src)); + +#ifdef DEBUG + // When this is called from dom::ReparentWrapper() there will be no native + // set for |dst|. Eventually it will be set to that of |src|. This will + // prevent attachExpandoObject() from preserving the wrapper, but this is + // not a problem because in this case the wrapper will already have been + // preserved when expandos were originally added to |src|. Assert the + // wrapper for |src| has been preserved if it has expandos set. + if (oldHead) { + nsISupports* identity = mozilla::dom::UnwrapDOMObjectToISupports(src); + if (identity) { + nsWrapperCache* cache = nullptr; + CallQueryInterface(identity, &cache); + MOZ_ASSERT_IF(cache, cache->PreservingWrapper()); + } + } +#endif + + while (oldHead) { + RootedObject exclusive(cx, JS_GetReservedSlot(oldHead, + JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL) + .toObjectOrNull()); + if (!JS_WrapObject(cx, &exclusive)) + return false; + RootedObject newHead(cx, attachExpandoObject(cx, dst, GetExpandoObjectPrincipal(oldHead), + exclusive)); + if (!JS_CopyPropertiesFrom(cx, newHead, oldHead)) + return false; + oldHead = JS_GetReservedSlot(oldHead, JSSLOT_EXPANDO_NEXT).toObjectOrNull(); + } + return true; +} + +void +ClearXrayExpandoSlots(JSObject* target, size_t slotIndex) +{ + if (!NS_IsMainThread()) { + // No Xrays + return; + } + + MOZ_ASSERT(GetXrayTraits(target) == &DOMXrayTraits::singleton); + RootingContext* rootingCx = RootingCx(); + RootedObject rootedTarget(rootingCx, target); + RootedObject head(rootingCx, + DOMXrayTraits::singleton.getExpandoChain(rootedTarget)); + while (head) { + MOZ_ASSERT(JSCLASS_RESERVED_SLOTS(js::GetObjectClass(head)) > slotIndex); + js::SetReservedSlot(head, slotIndex, UndefinedValue()); + head = js::GetReservedSlot(head, JSSLOT_EXPANDO_NEXT).toObjectOrNull(); + } +} + +JSObject* +EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(GetXrayTraits(wrapper) == &DOMXrayTraits::singleton); + MOZ_ASSERT(IsXrayWrapper(wrapper)); + + RootedObject target(cx, DOMXrayTraits::singleton.getTargetObject(wrapper)); + return DOMXrayTraits::singleton.ensureExpandoObject(cx, wrapper, target); +} + +const JSClass* +XrayTraits::getExpandoClass(JSContext* cx, HandleObject target) const +{ + return &DefaultXrayExpandoObjectClass; +} + +namespace XrayUtils { +bool CloneExpandoChain(JSContext* cx, JSObject* dstArg, JSObject* srcArg) +{ + RootedObject dst(cx, dstArg); + RootedObject src(cx, srcArg); + return GetXrayTraits(src)->cloneExpandoChain(cx, dst, src); +} +} // namespace XrayUtils + +static JSObject* +GetHolder(JSObject* obj) +{ + return &js::GetProxyExtra(obj, 0).toObject(); +} + +JSObject* +XrayTraits::getHolder(JSObject* wrapper) +{ + MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper)); + js::Value v = js::GetProxyExtra(wrapper, 0); + return v.isObject() ? &v.toObject() : nullptr; +} + +JSObject* +XrayTraits::ensureHolder(JSContext* cx, HandleObject wrapper) +{ + RootedObject holder(cx, getHolder(wrapper)); + if (holder) + return holder; + holder = createHolder(cx, wrapper); // virtual trap. + if (holder) + js::SetProxyExtra(wrapper, 0, ObjectValue(*holder)); + return holder; +} + +namespace XrayUtils { + +bool +IsXPCWNHolderClass(const JSClass* clasp) +{ + return clasp == &XPCWrappedNativeXrayTraits::HolderClass; +} + +} // namespace XrayUtils + +static nsGlobalWindow* +AsWindow(JSContext* cx, JSObject* wrapper) +{ + // We want to use our target object here, since we don't want to be + // doing a security check while unwrapping. + JSObject* target = XrayTraits::getTargetObject(wrapper); + return WindowOrNull(target); +} + +static bool +IsWindow(JSContext* cx, JSObject* wrapper) +{ + return !!AsWindow(cx, wrapper); +} + +void +XPCWrappedNativeXrayTraits::preserveWrapper(JSObject* target) +{ + XPCWrappedNative* wn = XPCWrappedNative::Get(target); + RefPtr ci; + CallQueryInterface(wn->Native(), getter_AddRefs(ci)); + if (ci) + ci->PreserveWrapper(wn->Native()); +} + +static bool +XrayToString(JSContext* cx, unsigned argc, JS::Value* vp); + +bool +XPCWrappedNativeXrayTraits::resolveNativeProperty(JSContext* cx, HandleObject wrapper, + HandleObject holder, HandleId id, + MutableHandle desc) +{ + MOZ_ASSERT(js::GetObjectJSClass(holder) == &HolderClass); + + desc.object().set(nullptr); + + // This will do verification and the method lookup for us. + RootedObject target(cx, getTargetObject(wrapper)); + XPCCallContext ccx(cx, target, nullptr, id); + + // There are no native numeric (or symbol-keyed) properties, so we can + // shortcut here. We will not find the property. + if (!JSID_IS_STRING(id)) + return true; + + XPCNativeInterface* iface; + XPCNativeMember* member; + XPCWrappedNative* wn = getWN(wrapper); + + if (ccx.GetWrapper() != wn || !wn->IsValid()) { + return true; + } + + if (!(iface = ccx.GetInterface()) || !(member = ccx.GetMember())) { + if (id != nsXPConnect::GetContextInstance()->GetStringID(XPCJSContext::IDX_TO_STRING)) + return true; + + JSFunction* toString = JS_NewFunction(cx, XrayToString, 0, 0, "toString"); + if (!toString) + return false; + + FillPropertyDescriptor(desc, wrapper, 0, + ObjectValue(*JS_GetFunctionObject(toString))); + + return JS_DefinePropertyById(cx, holder, id, desc) && + JS_GetOwnPropertyDescriptorById(cx, holder, id, desc); + } + + desc.object().set(holder); + desc.setAttributes(JSPROP_ENUMERATE); + desc.setGetter(nullptr); + desc.setSetter(nullptr); + desc.value().setUndefined(); + + RootedValue fval(cx, JS::UndefinedValue()); + if (member->IsConstant()) { + if (!member->GetConstantValue(ccx, iface, desc.value().address())) { + JS_ReportErrorASCII(cx, "Failed to convert constant native property to JS value"); + return false; + } + } else if (member->IsAttribute()) { + // This is a getter/setter. Clone a function for it. + if (!member->NewFunctionObject(ccx, iface, wrapper, fval.address())) { + JS_ReportErrorASCII(cx, "Failed to clone function object for native getter/setter"); + return false; + } + + unsigned attrs = desc.attributes(); + attrs |= JSPROP_GETTER; + if (member->IsWritableAttribute()) + attrs |= JSPROP_SETTER; + + // Make the property shared on the holder so no slot is allocated + // for it. This avoids keeping garbage alive through that slot. + attrs |= JSPROP_SHARED; + desc.setAttributes(attrs); + } else { + // This is a method. Clone a function for it. + if (!member->NewFunctionObject(ccx, iface, wrapper, desc.value().address())) { + JS_ReportErrorASCII(cx, "Failed to clone function object for native function"); + return false; + } + + // Without a wrapper the function would live on the prototype. Since we + // don't have one, we have to avoid calling the scriptable helper's + // GetProperty method for this property, so null out the getter and + // setter here explicitly. + desc.setGetter(nullptr); + desc.setSetter(nullptr); + } + + if (!JS_WrapValue(cx, desc.value()) || !JS_WrapValue(cx, &fval)) + return false; + + if (desc.hasGetterObject()) + desc.setGetterObject(&fval.toObject()); + if (desc.hasSetterObject()) + desc.setSetterObject(&fval.toObject()); + + return JS_DefinePropertyById(cx, holder, id, desc); +} + +static bool +wrappedJSObject_getter(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (!args.thisv().isObject()) { + JS_ReportErrorASCII(cx, "This value not an object"); + return false; + } + RootedObject wrapper(cx, &args.thisv().toObject()); + if (!IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper) || + !WrapperFactory::AllowWaiver(wrapper)) { + JS_ReportErrorASCII(cx, "Unexpected object"); + return false; + } + + args.rval().setObject(*wrapper); + + return WrapperFactory::WaiveXrayAndWrap(cx, args.rval()); +} + +bool +XrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, + HandleObject wrapper, HandleObject holder, HandleId id, + MutableHandle desc) +{ + desc.object().set(nullptr); + RootedObject target(cx, getTargetObject(wrapper)); + RootedObject expando(cx); + if (!getExpandoObject(cx, target, wrapper, &expando)) + return false; + + // Check for expando properties first. Note that the expando object lives + // in the target compartment. + bool found = false; + if (expando) { + JSAutoCompartment ac(cx, expando); + if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) + return false; + found = !!desc.object(); + } + + // Next, check for ES builtins. + if (!found && JS_IsGlobalObject(target)) { + JSProtoKey key = JS_IdToProtoKey(cx, id); + JSAutoCompartment ac(cx, target); + if (key != JSProto_Null) { + MOZ_ASSERT(key < JSProto_LIMIT); + RootedObject constructor(cx); + if (!JS_GetClassObject(cx, key, &constructor)) + return false; + MOZ_ASSERT(constructor); + desc.value().set(ObjectValue(*constructor)); + found = true; + } else if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_EVAL)) { + RootedObject eval(cx); + if (!js::GetOriginalEval(cx, target, &eval)) + return false; + desc.value().set(ObjectValue(*eval)); + found = true; + } + } + + if (found) { + if (!JS_WrapPropertyDescriptor(cx, desc)) + return false; + // Pretend the property lives on the wrapper. + desc.object().set(wrapper); + return true; + } + + // Handle .wrappedJSObject for subsuming callers. This should move once we + // sort out own-ness for the holder. + if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) && + WrapperFactory::AllowWaiver(wrapper)) + { + if (!JS_AlreadyHasOwnPropertyById(cx, holder, id, &found)) + return false; + if (!found && !JS_DefinePropertyById(cx, holder, id, UndefinedHandleValue, + JSPROP_ENUMERATE | JSPROP_SHARED, + wrappedJSObject_getter)) { + return false; + } + if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) + return false; + desc.object().set(wrapper); + return true; + } + + return true; +} + +bool +XPCWrappedNativeXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, + HandleObject wrapper, HandleObject holder, + HandleId id, + MutableHandle desc) +{ + // Call the common code. + bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, + id, desc); + if (!ok || desc.object()) + return ok; + + // Xray wrappers don't use the regular wrapper hierarchy, so we should be + // in the wrapper's compartment here, not the wrappee. + MOZ_ASSERT(js::IsObjectInContextCompartment(wrapper, cx)); + + return JS_GetOwnPropertyDescriptorById(cx, holder, id, desc); +} + +bool +XPCWrappedNativeXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags, + AutoIdVector& props) +{ + // Force all native properties to be materialized onto the wrapped native. + AutoIdVector wnProps(cx); + { + RootedObject target(cx, singleton.getTargetObject(wrapper)); + JSAutoCompartment ac(cx, target); + if (!js::GetPropertyKeys(cx, target, flags, &wnProps)) + return false; + } + + // Go through the properties we found on the underlying object and see if + // they appear on the XrayWrapper. If it throws (which may happen if the + // wrapper is a SecurityWrapper), just clear the exception and move on. + MOZ_ASSERT(!JS_IsExceptionPending(cx)); + if (!props.reserve(wnProps.length())) + return false; + for (size_t n = 0; n < wnProps.length(); ++n) { + RootedId id(cx, wnProps[n]); + bool hasProp; + if (JS_HasPropertyById(cx, wrapper, id, &hasProp) && hasProp) + props.infallibleAppend(id); + JS_ClearPendingException(cx); + } + return true; +} + +JSObject* +XPCWrappedNativeXrayTraits::createHolder(JSContext* cx, JSObject* wrapper) +{ + return JS_NewObjectWithGivenProto(cx, &HolderClass, nullptr); +} + +bool +XPCWrappedNativeXrayTraits::call(JSContext* cx, HandleObject wrapper, + const JS::CallArgs& args, + const js::Wrapper& baseInstance) +{ + // Run the call hook of the wrapped native. + XPCWrappedNative* wn = getWN(wrapper); + if (NATIVE_HAS_FLAG(wn, WantCall)) { + XPCCallContext ccx(cx, wrapper, nullptr, JSID_VOIDHANDLE, args.length(), + args.array(), args.rval().address()); + if (!ccx.IsValid()) + return false; + bool ok = true; + nsresult rv = wn->GetScriptableInfo()->GetCallback()->Call( + wn, cx, wrapper, args, &ok); + if (NS_FAILED(rv)) { + if (ok) + XPCThrower::Throw(rv, cx); + return false; + } + } + + return true; + +} + +bool +XPCWrappedNativeXrayTraits::construct(JSContext* cx, HandleObject wrapper, + const JS::CallArgs& args, + const js::Wrapper& baseInstance) +{ + // Run the construct hook of the wrapped native. + XPCWrappedNative* wn = getWN(wrapper); + if (NATIVE_HAS_FLAG(wn, WantConstruct)) { + XPCCallContext ccx(cx, wrapper, nullptr, JSID_VOIDHANDLE, args.length(), + args.array(), args.rval().address()); + if (!ccx.IsValid()) + return false; + bool ok = true; + nsresult rv = wn->GetScriptableInfo()->GetCallback()->Construct( + wn, cx, wrapper, args, &ok); + if (NS_FAILED(rv)) { + if (ok) + XPCThrower::Throw(rv, cx); + return false; + } + } + + return true; + +} + +bool +DOMXrayTraits::resolveOwnProperty(JSContext* cx, const Wrapper& jsWrapper, HandleObject wrapper, + HandleObject holder, HandleId id, + MutableHandle desc) +{ + // Call the common code. + bool ok = XrayTraits::resolveOwnProperty(cx, jsWrapper, wrapper, holder, id, desc); + if (!ok || desc.object()) + return ok; + + // Check for indexed access on a window. + uint32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + nsGlobalWindow* win = AsWindow(cx, wrapper); + // Note: As() unwraps outer windows to get to the inner window. + if (win) { + nsCOMPtr subframe = win->IndexedGetter(index); + if (subframe) { + subframe->EnsureInnerWindow(); + nsGlobalWindow* global = nsGlobalWindow::Cast(subframe); + JSObject* obj = global->FastGetGlobalJSObject(); + if (MOZ_UNLIKELY(!obj)) { + // It's gone? + return xpc::Throw(cx, NS_ERROR_FAILURE); + } + ExposeObjectToActiveJS(obj); + desc.value().setObject(*obj); + FillPropertyDescriptor(desc, wrapper, true); + return JS_WrapPropertyDescriptor(cx, desc); + } + } + } + + if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) + return false; + if (desc.object()) { + desc.object().set(wrapper); + return true; + } + + RootedObject obj(cx, getTargetObject(wrapper)); + bool cacheOnHolder; + if (!XrayResolveOwnProperty(cx, wrapper, obj, id, desc, cacheOnHolder)) + return false; + + MOZ_ASSERT(!desc.object() || desc.object() == wrapper, "What did we resolve this on?"); + + if (!desc.object() || !cacheOnHolder) + return true; + + return JS_DefinePropertyById(cx, holder, id, desc) && + JS_GetOwnPropertyDescriptorById(cx, holder, id, desc); +} + +bool +DOMXrayTraits::delete_(JSContext* cx, JS::HandleObject wrapper, + JS::HandleId id, JS::ObjectOpResult& result) +{ + RootedObject target(cx, getTargetObject(wrapper)); + return XrayDeleteNamedProperty(cx, wrapper, target, id, result); +} + +bool +DOMXrayTraits::defineProperty(JSContext* cx, HandleObject wrapper, HandleId id, + Handle desc, + Handle existingDesc, + JS::ObjectOpResult& result, bool* defined) +{ + // Check for an indexed property on a Window. If that's happening, do + // nothing but claim we defined it so it won't get added as an expando. + if (IsWindow(cx, wrapper)) { + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + *defined = true; + return result.succeed(); + } + } + + JS::Rooted obj(cx, getTargetObject(wrapper)); + return XrayDefineProperty(cx, wrapper, obj, id, desc, result, defined); +} + +bool +DOMXrayTraits::enumerateNames(JSContext* cx, HandleObject wrapper, unsigned flags, + AutoIdVector& props) +{ + JS::Rooted obj(cx, getTargetObject(wrapper)); + return XrayOwnPropertyKeys(cx, wrapper, obj, flags, props); +} + +bool +DOMXrayTraits::call(JSContext* cx, HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) +{ + RootedObject obj(cx, getTargetObject(wrapper)); + const js::Class* clasp = js::GetObjectClass(obj); + // What we have is either a WebIDL interface object, a WebIDL prototype + // object, or a WebIDL instance object. WebIDL prototype objects never have + // a clasp->call. WebIDL interface objects we want to invoke on the xray + // compartment. WebIDL instance objects either don't have a clasp->call or + // are using "legacycaller", which basically means plug-ins. We want to + // call those on the content compartment. + if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) { + if (JSNative call = clasp->getCall()) { + // call it on the Xray compartment + if (!call(cx, args.length(), args.base())) + return false; + } else { + RootedValue v(cx, ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; + } + } else { + // This is only reached for WebIDL instance objects, and in practice + // only for plugins. Just call them on the content compartment. + if (!baseInstance.call(cx, wrapper, args)) + return false; + } + return JS_WrapValue(cx, args.rval()); +} + +bool +DOMXrayTraits::construct(JSContext* cx, HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) +{ + RootedObject obj(cx, getTargetObject(wrapper)); + MOZ_ASSERT(mozilla::dom::HasConstructor(obj)); + const js::Class* clasp = js::GetObjectClass(obj); + // See comments in DOMXrayTraits::call() explaining what's going on here. + if (clasp->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS) { + if (JSNative construct = clasp->getConstruct()) { + if (!construct(cx, args.length(), args.base())) + return false; + } else { + RootedValue v(cx, ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; + } + } else { + if (!baseInstance.construct(cx, wrapper, args)) + return false; + } + if (!args.rval().isObject() || !JS_WrapValue(cx, args.rval())) + return false; + return true; +} + +bool +DOMXrayTraits::getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, + JS::MutableHandleObject protop) +{ + return mozilla::dom::XrayGetNativeProto(cx, target, protop); +} + +void +DOMXrayTraits::preserveWrapper(JSObject* target) +{ + nsISupports* identity = mozilla::dom::UnwrapDOMObjectToISupports(target); + if (!identity) + return; + nsWrapperCache* cache = nullptr; + CallQueryInterface(identity, &cache); + if (cache) + cache->PreserveWrapper(identity); +} + +JSObject* +DOMXrayTraits::createHolder(JSContext* cx, JSObject* wrapper) +{ + return JS_NewObjectWithGivenProto(cx, nullptr, nullptr); +} + +const JSClass* +DOMXrayTraits::getExpandoClass(JSContext* cx, HandleObject target) const +{ + return XrayGetExpandoClass(cx, target); +} + +namespace XrayUtils { + +JSObject* +GetNativePropertiesObject(JSContext* cx, JSObject* wrapper) +{ + MOZ_ASSERT(js::IsWrapper(wrapper) && WrapperFactory::IsXrayWrapper(wrapper), + "bad object passed in"); + + JSObject* holder = GetHolder(wrapper); + MOZ_ASSERT(holder, "uninitialized wrapper being used?"); + return holder; +} + +bool +HasNativeProperty(JSContext* cx, HandleObject wrapper, HandleId id, bool* hasProp) +{ + MOZ_ASSERT(WrapperFactory::IsXrayWrapper(wrapper)); + XrayTraits* traits = GetXrayTraits(wrapper); + MOZ_ASSERT(traits); + RootedObject holder(cx, traits->ensureHolder(cx, wrapper)); + NS_ENSURE_TRUE(holder, false); + *hasProp = false; + Rooted desc(cx); + const Wrapper* handler = Wrapper::wrapperHandler(wrapper); + + // Try resolveOwnProperty. + if (!traits->resolveOwnProperty(cx, *handler, wrapper, holder, id, &desc)) + return false; + if (desc.object()) { + *hasProp = true; + return true; + } + + // Try the holder. + bool found = false; + if (!JS_AlreadyHasOwnPropertyById(cx, holder, id, &found)) + return false; + if (found) { + *hasProp = true; + return true; + } + + // Try resolveNativeProperty. + if (!traits->resolveNativeProperty(cx, wrapper, holder, id, &desc)) + return false; + *hasProp = !!desc.object(); + return true; +} + +} // namespace XrayUtils + +static bool +XrayToString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + if (!args.thisv().isObject()) { + JS_ReportErrorASCII(cx, "XrayToString called on an incompatible object"); + return false; + } + + RootedObject wrapper(cx, &args.thisv().toObject()); + if (!wrapper) + return false; + if (IsWrapper(wrapper) && + GetProxyHandler(wrapper) == &sandboxCallableProxyHandler) { + wrapper = xpc::SandboxCallableProxyHandler::wrappedObject(wrapper); + } + if (!IsWrapper(wrapper) || !WrapperFactory::IsXrayWrapper(wrapper)) { + JS_ReportErrorASCII(cx, "XrayToString called on an incompatible object"); + return false; + } + + RootedObject obj(cx, XrayTraits::getTargetObject(wrapper)); + if (GetXrayType(obj) != XrayForWrappedNative) { + JS_ReportErrorASCII(cx, "XrayToString called on an incompatible object"); + return false; + } + + static const char start[] = "[object XrayWrapper "; + static const char end[] = "]"; + nsAutoString result; + result.AppendASCII(start); + + XPCCallContext ccx(cx, obj); + XPCWrappedNative* wn = XPCWrappedNativeXrayTraits::getWN(wrapper); + char* wrapperStr = wn->ToString(); + if (!wrapperStr) { + JS_ReportOutOfMemory(cx); + return false; + } + result.AppendASCII(wrapperStr); + JS_smprintf_free(wrapperStr); + + result.AppendASCII(end); + + JSString* str = JS_NewUCStringCopyN(cx, result.get(), result.Length()); + if (!str) + return false; + + args.rval().setString(str); + return true; +} + +template +bool +XrayWrapper::preventExtensions(JSContext* cx, HandleObject wrapper, + ObjectOpResult& result) const +{ + // Xray wrappers are supposed to provide a clean view of the target + // reflector, hiding any modifications by script in the target scope. So + // even if that script freezes the reflector, we don't want to make that + // visible to the caller. DOM reflectors are always extensible by default, + // so we can just return failure here. + return result.failCantPreventExtensions(); +} + +template +bool +XrayWrapper::isExtensible(JSContext* cx, JS::Handle wrapper, + bool* extensible) const +{ + // See above. + *extensible = true; + return true; +} + +template +bool +XrayWrapper::getPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, + JS::MutableHandle desc) + const +{ + assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET | + BaseProxyHandler::GET_PROPERTY_DESCRIPTOR); + RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper)); + + if (!holder) + return false; + + // Ordering is important here. + // + // We first need to call resolveOwnProperty, even before checking the holder, + // because there might be a new dynamic |own| property that appears and + // shadows a previously-resolved non-own property that we cached on the + // holder. This can happen with indexed properties on NodeLists, for example, + // which are |own| value props. + // + // resolveOwnProperty may or may not cache what it finds on the holder, + // depending on how ephemeral it decides the property is. XPCWN |own| + // properties generally end up on the holder via Resolve, whereas + // NodeList |own| properties don't get defined on the holder, since they're + // supposed to be dynamic. This means that we have to first check the result + // of resolveOwnProperty, and _then_, if that comes up blank, check the + // holder for any cached native properties. + // + // Finally, we call resolveNativeProperty, which checks non-own properties, + // and unconditionally caches what it finds on the holder. + + // Check resolveOwnProperty. + if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc)) + return false; + + // Check the holder. + if (!desc.object() && !JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) + return false; + if (desc.object()) { + desc.object().set(wrapper); + return true; + } + + // Nothing in the cache. Call through, and cache the result. + if (!Traits::singleton.resolveNativeProperty(cx, wrapper, holder, id, desc)) + return false; + + // We need to handle named access on the Window somewhere other than + // Traits::resolveOwnProperty, because per spec it happens on the Global + // Scope Polluter and thus the resulting properties are non-|own|. However, + // we're set up (above) to cache (on the holder) anything that comes out of + // resolveNativeProperty, which we don't want for something dynamic like + // named access. So we just handle it separately here. + nsGlobalWindow* win = nullptr; + if (!desc.object() && + JSID_IS_STRING(id) && + (win = AsWindow(cx, wrapper))) + { + nsAutoJSString name; + if (!name.init(cx, JSID_TO_STRING(id))) + return false; + if (nsCOMPtr childDOMWin = win->GetChildWindow(name)) { + auto* cwin = nsGlobalWindow::Cast(childDOMWin); + JSObject* childObj = cwin->FastGetGlobalJSObject(); + if (MOZ_UNLIKELY(!childObj)) + return xpc::Throw(cx, NS_ERROR_FAILURE); + ExposeObjectToActiveJS(childObj); + FillPropertyDescriptor(desc, wrapper, ObjectValue(*childObj), + /* readOnly = */ true); + return JS_WrapPropertyDescriptor(cx, desc); + } + } + + // If we still have nothing, we're done. + if (!desc.object()) + return true; + + if (!JS_DefinePropertyById(cx, holder, id, desc) || + !JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) + { + return false; + } + MOZ_ASSERT(desc.object()); + desc.object().set(wrapper); + return true; +} + +template +bool +XrayWrapper::getOwnPropertyDescriptor(JSContext* cx, HandleObject wrapper, HandleId id, + JS::MutableHandle desc) + const +{ + assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::GET | BaseProxyHandler::SET | + BaseProxyHandler::GET_PROPERTY_DESCRIPTOR); + RootedObject holder(cx, Traits::singleton.ensureHolder(cx, wrapper)); + + if (!Traits::singleton.resolveOwnProperty(cx, *this, wrapper, holder, id, desc)) + return false; + if (desc.object()) + desc.object().set(wrapper); + return true; +} + +// Consider what happens when chrome does |xray.expando = xray.wrappedJSObject|. +// +// Since the expando comes from the target compartment, wrapping it back into +// the target compartment to define it on the expando object ends up stripping +// off the Xray waiver that gives |xray| and |xray.wrappedJSObject| different +// identities. This is generally the right thing to do when wrapping across +// compartments, but is incorrect in the special case of the Xray expando +// object. Manually re-apply Xrays if necessary. +// +// NB: In order to satisfy the invariants of WaiveXray, we need to pass +// in an object sans security wrapper, which means we need to strip off any +// potential same-compartment security wrapper that may have been applied +// to the content object. This is ok, because the the expando object is only +// ever accessed by code across the compartment boundary. +static bool +RecreateLostWaivers(JSContext* cx, const PropertyDescriptor* orig, + MutableHandle wrapped) +{ + // Compute whether the original objects were waived, and implicitly, whether + // they were objects at all. + bool valueWasWaived = + orig->value.isObject() && + WrapperFactory::HasWaiveXrayFlag(&orig->value.toObject()); + bool getterWasWaived = + (orig->attrs & JSPROP_GETTER) && orig->getter && + WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->getter)); + bool setterWasWaived = + (orig->attrs & JSPROP_SETTER) && orig->setter && + WrapperFactory::HasWaiveXrayFlag(JS_FUNC_TO_DATA_PTR(JSObject*, orig->setter)); + + // Recreate waivers. Note that for value, we need an extra UncheckedUnwrap + // to handle same-compartment security wrappers (see above). This should + // never happen for getters/setters. + + RootedObject rewaived(cx); + if (valueWasWaived && !IsCrossCompartmentWrapper(&wrapped.value().toObject())) { + rewaived = &wrapped.value().toObject(); + rewaived = WrapperFactory::WaiveXray(cx, UncheckedUnwrap(rewaived)); + NS_ENSURE_TRUE(rewaived, false); + wrapped.value().set(ObjectValue(*rewaived)); + } + if (getterWasWaived && !IsCrossCompartmentWrapper(wrapped.getterObject())) { + MOZ_ASSERT(CheckedUnwrap(wrapped.getterObject())); + rewaived = WrapperFactory::WaiveXray(cx, wrapped.getterObject()); + NS_ENSURE_TRUE(rewaived, false); + wrapped.setGetterObject(rewaived); + } + if (setterWasWaived && !IsCrossCompartmentWrapper(wrapped.setterObject())) { + MOZ_ASSERT(CheckedUnwrap(wrapped.setterObject())); + rewaived = WrapperFactory::WaiveXray(cx, wrapped.setterObject()); + NS_ENSURE_TRUE(rewaived, false); + wrapped.setSetterObject(rewaived); + } + + return true; +} + +template +bool +XrayWrapper::defineProperty(JSContext* cx, HandleObject wrapper, + HandleId id, Handle desc, + ObjectOpResult& result) const +{ + assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::SET); + + Rooted existing_desc(cx); + if (!JS_GetPropertyDescriptorById(cx, wrapper, id, &existing_desc)) + return false; + + // Note that the check here is intended to differentiate between own and + // non-own properties, since the above lookup is not limited to own + // properties. At present, this may not always do the right thing because + // we often lie (sloppily) about where we found properties and set + // desc.object() to |wrapper|. Once we fully fix our Xray prototype semantics, + // this should work as intended. + if (existing_desc.object() == wrapper && !existing_desc.configurable()) { + // We have a non-configurable property. See if the caller is trying to + // re-configure it in any way other than making it non-writable. + if (existing_desc.isAccessorDescriptor() || desc.isAccessorDescriptor() || + (desc.hasEnumerable() && existing_desc.enumerable() != desc.enumerable()) || + (desc.hasWritable() && !existing_desc.writable() && desc.writable())) + { + // We should technically report non-configurability in strict mode, but + // doing that via JSAPI used to be a lot of trouble. See bug 1135997. + return result.succeed(); + } + if (!existing_desc.writable()) { + // Same as the above for non-writability. + return result.succeed(); + } + } + + bool defined = false; + if (!Traits::singleton.defineProperty(cx, wrapper, id, desc, existing_desc, result, &defined)) + return false; + if (defined) + return true; + + // We're placing an expando. The expando objects live in the target + // compartment, so we need to enter it. + RootedObject target(cx, Traits::singleton.getTargetObject(wrapper)); + JSAutoCompartment ac(cx, target); + + // Grab the relevant expando object. + RootedObject expandoObject(cx, Traits::singleton.ensureExpandoObject(cx, wrapper, + target)); + if (!expandoObject) + return false; + + // Wrap the property descriptor for the target compartment. + Rooted wrappedDesc(cx, desc); + if (!JS_WrapPropertyDescriptor(cx, &wrappedDesc)) + return false; + + // Fix up Xray waivers. + if (!RecreateLostWaivers(cx, desc.address(), &wrappedDesc)) + return false; + + return JS_DefinePropertyById(cx, expandoObject, id, wrappedDesc, result); +} + +template +bool +XrayWrapper::ownPropertyKeys(JSContext* cx, HandleObject wrapper, + AutoIdVector& props) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); + return getPropertyKeys(cx, wrapper, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props); +} + +template +bool +XrayWrapper::delete_(JSContext* cx, HandleObject wrapper, + HandleId id, ObjectOpResult& result) const +{ + assertEnteredPolicy(cx, wrapper, id, BaseProxyHandler::SET); + + // Check the expando object. + RootedObject target(cx, Traits::getTargetObject(wrapper)); + RootedObject expando(cx); + if (!Traits::singleton.getExpandoObject(cx, target, wrapper, &expando)) + return false; + + if (expando) { + JSAutoCompartment ac(cx, expando); + bool hasProp; + if (!JS_HasPropertyById(cx, expando, id, &hasProp)) { + return false; + } + if (hasProp) { + return JS_DeletePropertyById(cx, expando, id, result); + } + } + + return Traits::singleton.delete_(cx, wrapper, id, result); +} + +template +bool +XrayWrapper::get(JSContext* cx, HandleObject wrapper, + HandleValue receiver, HandleId id, + MutableHandleValue vp) const +{ + // Skip our Base if it isn't already ProxyHandler. + // NB: None of the functions we call are prepared for the receiver not + // being the wrapper, so ignore the receiver here. + RootedValue thisv(cx); + if (Traits::HasPrototype) + thisv = receiver; + else + thisv.setObject(*wrapper); + + // This uses getPropertyDescriptor for backward compatibility with + // the old BaseProxyHandler::get implementation. + Rooted desc(cx); + if (!getPropertyDescriptor(cx, wrapper, id, &desc)) + return false; + desc.assertCompleteIfFound(); + + if (!desc.object()) { + vp.setUndefined(); + return true; + } + + // Everything after here follows [[Get]] for ordinary objects. + if (desc.isDataDescriptor()) { + vp.set(desc.value()); + return true; + } + + MOZ_ASSERT(desc.isAccessorDescriptor()); + RootedObject getter(cx, desc.getterObject()); + + if (!getter) { + vp.setUndefined(); + return true; + } + + return Call(cx, thisv, getter, HandleValueArray::empty(), vp); +} + +template +bool +XrayWrapper::set(JSContext* cx, HandleObject wrapper, HandleId id, HandleValue v, + HandleValue receiver, ObjectOpResult& result) const +{ + MOZ_ASSERT(!Traits::HasPrototype); + // Skip our Base if it isn't already BaseProxyHandler. + // NB: None of the functions we call are prepared for the receiver not + // being the wrapper, so ignore the receiver here. + RootedValue wrapperValue(cx, ObjectValue(*wrapper)); + return js::BaseProxyHandler::set(cx, wrapper, id, v, wrapperValue, result); +} + +template +bool +XrayWrapper::has(JSContext* cx, HandleObject wrapper, + HandleId id, bool* bp) const +{ + // This uses getPropertyDescriptor for backward compatibility with + // the old BaseProxyHandler::has implementation. + Rooted desc(cx); + if (!getPropertyDescriptor(cx, wrapper, id, &desc)) + return false; + + *bp = !!desc.object(); + return true; +} + +template +bool +XrayWrapper::hasOwn(JSContext* cx, HandleObject wrapper, + HandleId id, bool* bp) const +{ + // Skip our Base if it isn't already ProxyHandler. + return js::BaseProxyHandler::hasOwn(cx, wrapper, id, bp); +} + +template +bool +XrayWrapper::getOwnEnumerablePropertyKeys(JSContext* cx, + HandleObject wrapper, + AutoIdVector& props) const +{ + // Skip our Base if it isn't already ProxyHandler. + return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, wrapper, props); +} + +template +bool +XrayWrapper::enumerate(JSContext* cx, HandleObject wrapper, + MutableHandleObject objp) const +{ + // Skip our Base if it isn't already ProxyHandler. + return js::BaseProxyHandler::enumerate(cx, wrapper, objp); +} + +template +bool +XrayWrapper::call(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::CALL); + // Hard cast the singleton since SecurityWrapper doesn't have one. + return Traits::call(cx, wrapper, args, Base::singleton); +} + +template +bool +XrayWrapper::construct(JSContext* cx, HandleObject wrapper, const JS::CallArgs& args) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::CALL); + // Hard cast the singleton since SecurityWrapper doesn't have one. + return Traits::construct(cx, wrapper, args, Base::singleton); +} + +template +bool +XrayWrapper::getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, js::ESClass* cls) const +{ + return Traits::getBuiltinClass(cx, wrapper, Base::singleton, cls); +} + +template +const char* +XrayWrapper::className(JSContext* cx, HandleObject wrapper) const +{ + return Traits::className(cx, wrapper, Base::singleton); +} + +template +bool +XrayWrapper::getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleObject protop) const +{ + // We really only want this override for non-SecurityWrapper-inheriting + // |Base|. But doing that statically with templates requires partial method + // specializations (and therefore a helper class), which is all more trouble + // than it's worth. Do a dynamic check. + if (Base::hasSecurityPolicy()) + return Base::getPrototype(cx, wrapper, protop); + + RootedObject target(cx, Traits::getTargetObject(wrapper)); + RootedObject expando(cx); + if (!Traits::singleton.getExpandoObject(cx, target, wrapper, &expando)) + return false; + + // We want to keep the Xray's prototype distinct from that of content, but + // only if there's been a set. If there's not an expando, or the expando + // slot is |undefined|, hand back the default proto, appropriately wrapped. + + RootedValue v(cx); + if (expando) { + JSAutoCompartment ac(cx, expando); + v = JS_GetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE); + } + if (v.isUndefined()) + return getPrototypeHelper(cx, wrapper, target, protop); + + protop.set(v.toObjectOrNull()); + return JS_WrapObject(cx, protop); +} + +template +bool +XrayWrapper::setPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject proto, JS::ObjectOpResult& result) const +{ + // Do this only for non-SecurityWrapper-inheriting |Base|. See the comment + // in getPrototype(). + if (Base::hasSecurityPolicy()) + return Base::setPrototype(cx, wrapper, proto, result); + + RootedObject target(cx, Traits::getTargetObject(wrapper)); + RootedObject expando(cx, Traits::singleton.ensureExpandoObject(cx, wrapper, target)); + if (!expando) + return false; + + // The expando lives in the target's compartment, so do our installation there. + JSAutoCompartment ac(cx, target); + + RootedValue v(cx, ObjectOrNullValue(proto)); + if (!JS_WrapValue(cx, &v)) + return false; + JS_SetReservedSlot(expando, JSSLOT_EXPANDO_PROTOTYPE, v); + return result.succeed(); +} + +template +bool +XrayWrapper::getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject wrapper, + bool* isOrdinary, + JS::MutableHandleObject protop) const +{ + // We want to keep the Xray's prototype distinct from that of content, but + // only if there's been a set. This different-prototype-over-time behavior + // means that the [[GetPrototypeOf]] trap *can't* be ECMAScript's ordinary + // [[GetPrototypeOf]]. This also covers cross-origin Window behavior that + // per + // must be non-ordinary. + *isOrdinary = false; + return true; +} + +template +bool +XrayWrapper::setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper, + bool* succeeded) const +{ + // For now, lacking an obvious place to store a bit, prohibit making an + // Xray's [[Prototype]] immutable. We can revisit this (or maybe give all + // Xrays immutable [[Prototype]], because who does this, really?) later if + // necessary. + *succeeded = false; + return true; +} + +template +bool +XrayWrapper::getPropertyKeys(JSContext* cx, HandleObject wrapper, unsigned flags, + AutoIdVector& props) const +{ + assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE); + + // Enumerate expando properties first. Note that the expando object lives + // in the target compartment. + RootedObject target(cx, Traits::singleton.getTargetObject(wrapper)); + RootedObject expando(cx); + if (!Traits::singleton.getExpandoObject(cx, target, wrapper, &expando)) + return false; + + if (expando) { + JSAutoCompartment ac(cx, expando); + if (!js::GetPropertyKeys(cx, expando, flags, &props)) + return false; + } + + return Traits::singleton.enumerateNames(cx, wrapper, flags, props); +} + +/* + * The Permissive / Security variants should be used depending on whether the + * compartment of the wrapper is guranteed to subsume the compartment of the + * wrapped object (i.e. - whether it is safe from a security perspective to + * unwrap the wrapper). + */ + +template +const xpc::XrayWrapper +xpc::XrayWrapper::singleton(0); + +template class PermissiveXrayXPCWN; +template class SecurityXrayXPCWN; +template class PermissiveXrayDOM; +template class SecurityXrayDOM; +template class PermissiveXrayJS; +template class PermissiveXrayOpaque; + +} // namespace xpc diff --git a/js/xpconnect/wrappers/XrayWrapper.h b/js/xpconnect/wrappers/XrayWrapper.h new file mode 100644 index 000000000..5630982c2 --- /dev/null +++ b/js/xpconnect/wrappers/XrayWrapper.h @@ -0,0 +1,620 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set ts=8 sts=4 et sw=4 tw=99: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef XrayWrapper_h +#define XrayWrapper_h + +#include "mozilla/Attributes.h" + +#include "WrapperFactory.h" + +#include "jswrapper.h" +#include "js/Proxy.h" + +// Slot where Xray functions for Web IDL methods store a pointer to +// the Xray wrapper they're associated with. +#define XRAY_DOM_FUNCTION_PARENT_WRAPPER_SLOT 0 +// Slot where in debug builds Xray functions for Web IDL methods store +// a pointer to their themselves, just so we can assert that they're the +// sort of functions we expect. +#define XRAY_DOM_FUNCTION_NATIVE_SLOT_FOR_SELF 1 + +// Xray wrappers re-resolve the original native properties on the native +// object and always directly access to those properties. +// Because they work so differently from the rest of the wrapper hierarchy, +// we pull them out of the Wrapper inheritance hierarchy and create a +// little world around them. + +class nsIPrincipal; +class XPCWrappedNative; + +namespace xpc { + +namespace XrayUtils { + +bool IsXPCWNHolderClass(const JSClass* clasp); + +bool CloneExpandoChain(JSContext* cx, JSObject* src, JSObject* dst); + +bool +IsTransparent(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id); + +JSObject* +GetNativePropertiesObject(JSContext* cx, JSObject* wrapper); + +bool +HasNativeProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + bool* hasProp); +} // namespace XrayUtils + +enum XrayType { + XrayForDOMObject, + XrayForWrappedNative, + XrayForJSObject, + XrayForOpaqueObject, + NotXray +}; + +class XrayTraits +{ +public: + constexpr XrayTraits() {} + + static JSObject* getTargetObject(JSObject* wrapper) { + return js::UncheckedUnwrap(wrapper, /* stopAtWindowProxy = */ false); + } + + virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) = 0; + // NB: resolveOwnProperty may decide whether or not to cache what it finds + // on the holder. If the result is not cached, the lookup will happen afresh + // for each access, which is the right thing for things like dynamic NodeList + // properties. + virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, + JS::HandleObject wrapper, JS::HandleObject holder, + JS::HandleId id, JS::MutableHandle desc); + + bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::ObjectOpResult& result) { + return result.succeed(); + } + + static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance, + js::ESClass* cls) { + return baseInstance.getBuiltinClass(cx, wrapper, cls); + } + + static const char* className(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance) { + return baseInstance.className(cx, wrapper); + } + + virtual void preserveWrapper(JSObject* target) = 0; + + bool getExpandoObject(JSContext* cx, JS::HandleObject target, + JS::HandleObject consumer, JS::MutableHandleObject expandObject); + JSObject* ensureExpandoObject(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target); + + JSObject* getHolder(JSObject* wrapper); + JSObject* ensureHolder(JSContext* cx, JS::HandleObject wrapper); + virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) = 0; + + JSObject* getExpandoChain(JS::HandleObject obj); + bool setExpandoChain(JSContext* cx, JS::HandleObject obj, JS::HandleObject chain); + bool cloneExpandoChain(JSContext* cx, JS::HandleObject dst, JS::HandleObject src); + +protected: + // Get the JSClass we should use for our expando object. + virtual const JSClass* getExpandoClass(JSContext* cx, + JS::HandleObject target) const; + +private: + bool expandoObjectMatchesConsumer(JSContext* cx, JS::HandleObject expandoObject, + nsIPrincipal* consumerOrigin, + JS::HandleObject exclusiveGlobal); + bool getExpandoObjectInternal(JSContext* cx, JS::HandleObject target, + nsIPrincipal* origin, JSObject* exclusiveGlobal, + JS::MutableHandleObject expandoObject); + JSObject* attachExpandoObject(JSContext* cx, JS::HandleObject target, + nsIPrincipal* origin, + JS::HandleObject exclusiveGlobal); + + XrayTraits(XrayTraits&) = delete; + const XrayTraits& operator=(XrayTraits&) = delete; +}; + +class XPCWrappedNativeXrayTraits : public XrayTraits +{ +public: + enum { + HasPrototype = 0 + }; + + static const XrayType Type = XrayForWrappedNative; + + virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override; + virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override; + bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::Handle desc, + JS::Handle existingDesc, + JS::ObjectOpResult& result, bool* defined) + { + *defined = false; + return true; + } + virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags, + JS::AutoIdVector& props); + static bool call(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance); + static bool construct(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance); + + static XPCWrappedNative* getWN(JSObject* wrapper); + + virtual void preserveWrapper(JSObject* target) override; + + virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; + + static const JSClass HolderClass; + static XPCWrappedNativeXrayTraits singleton; +}; + +class DOMXrayTraits : public XrayTraits +{ +public: + constexpr DOMXrayTraits() = default; + + enum { + HasPrototype = 1 + }; + + static const XrayType Type = XrayForDOMObject; + + virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override + { + // Xrays for DOM binding objects have a prototype chain that consists of + // Xrays for the prototypes of the DOM binding object (ignoring changes + // in the prototype chain made by script, plugins or XBL). All properties for + // these Xrays are really own properties, either of the instance object or + // of the prototypes. + // FIXME https://bugzilla.mozilla.org/show_bug.cgi?id=1072482 + // This should really be: + // MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1"); + // but we can't do that yet because XrayUtils::HasNativeProperty calls this. + return true; + } + virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override; + + bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::ObjectOpResult& result); + + bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::Handle desc, + JS::Handle existingDesc, + JS::ObjectOpResult& result, bool* defined); + virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags, + JS::AutoIdVector& props); + static bool call(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance); + static bool construct(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance); + + static bool getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, + JS::MutableHandleObject protop); + + virtual void preserveWrapper(JSObject* target) override; + + virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; + + static DOMXrayTraits singleton; + +protected: + virtual const JSClass* getExpandoClass(JSContext* cx, + JS::HandleObject target) const override; +}; + +class JSXrayTraits : public XrayTraits +{ +public: + enum { + HasPrototype = 1 + }; + static const XrayType Type = XrayForJSObject; + + virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override + { + MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1"); + } + + virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override; + + bool delete_(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, JS::ObjectOpResult& result); + + bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::Handle desc, + JS::Handle existingDesc, + JS::ObjectOpResult& result, bool* defined); + + virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags, + JS::AutoIdVector& props); + + static bool call(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) + { + JSXrayTraits& self = JSXrayTraits::singleton; + JS::RootedObject holder(cx, self.ensureHolder(cx, wrapper)); + if (self.getProtoKey(holder) == JSProto_Function) + return baseInstance.call(cx, wrapper, args); + + JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; + } + + static bool construct(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance); + + bool getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, + JS::MutableHandleObject protop) + { + JS::RootedObject holder(cx, ensureHolder(cx, wrapper)); + JSProtoKey key = getProtoKey(holder); + if (isPrototype(holder)) { + JSProtoKey protoKey = js::InheritanceProtoKeyForStandardClass(key); + if (protoKey == JSProto_Null) { + protop.set(nullptr); + return true; + } + key = protoKey; + } + + { + JSAutoCompartment ac(cx, target); + if (!JS_GetClassPrototype(cx, key, protop)) + return false; + } + return JS_WrapObject(cx, protop); + } + + virtual void preserveWrapper(JSObject* target) override { + // In the case of pure JS objects, there is no underlying object, and + // the target is the canonical representation of state. If it gets + // collected, then expandos and such should be collected too. So there's + // nothing to do here. + } + + enum { + SLOT_PROTOKEY = 0, + SLOT_ISPROTOTYPE, + SLOT_CONSTRUCTOR_FOR, + SLOT_COUNT + }; + virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override; + + static JSProtoKey getProtoKey(JSObject* holder) { + int32_t key = js::GetReservedSlot(holder, SLOT_PROTOKEY).toInt32(); + return static_cast(key); + } + + static bool isPrototype(JSObject* holder) { + return js::GetReservedSlot(holder, SLOT_ISPROTOTYPE).toBoolean(); + } + + static JSProtoKey constructorFor(JSObject* holder) { + int32_t key = js::GetReservedSlot(holder, SLOT_CONSTRUCTOR_FOR).toInt32(); + return static_cast(key); + } + + // Operates in the wrapper compartment. + static bool getOwnPropertyFromWrapperIfSafe(JSContext* cx, + JS::HandleObject wrapper, + JS::HandleId id, + JS::MutableHandle desc); + + // Like the above, but operates in the target compartment. + static bool getOwnPropertyFromTargetIfSafe(JSContext* cx, + JS::HandleObject target, + JS::HandleObject wrapper, + JS::HandleId id, + JS::MutableHandle desc); + + static const JSClass HolderClass; + static JSXrayTraits singleton; +}; + +// These traits are used when the target is not Xrayable and we therefore want +// to make it opaque modulo the usual Xray machinery (like expandos and +// .wrappedJSObject). +class OpaqueXrayTraits : public XrayTraits +{ +public: + enum { + HasPrototype = 1 + }; + static const XrayType Type = XrayForOpaqueObject; + + virtual bool resolveNativeProperty(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override + { + MOZ_CRASH("resolveNativeProperty hook should never be called with HasPrototype = 1"); + } + + virtual bool resolveOwnProperty(JSContext* cx, const js::Wrapper& jsWrapper, JS::HandleObject wrapper, + JS::HandleObject holder, JS::HandleId id, + JS::MutableHandle desc) override; + + bool defineProperty(JSContext* cx, JS::HandleObject wrapper, JS::HandleId id, + JS::Handle desc, + JS::Handle existingDesc, + JS::ObjectOpResult& result, bool* defined) + { + *defined = false; + return true; + } + + virtual bool enumerateNames(JSContext* cx, JS::HandleObject wrapper, unsigned flags, + JS::AutoIdVector& props) + { + return true; + } + + static bool call(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) + { + JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; + } + + static bool construct(JSContext* cx, JS::HandleObject wrapper, + const JS::CallArgs& args, const js::Wrapper& baseInstance) + { + JS::RootedValue v(cx, JS::ObjectValue(*wrapper)); + js::ReportIsNotFunction(cx, v); + return false; + } + + bool getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, + JS::MutableHandleObject protop) + { + // Opaque wrappers just get targetGlobal.Object.prototype as their + // prototype. This is preferable to using a null prototype because it + // lets things like |toString| and |__proto__| work. + { + JSAutoCompartment ac(cx, target); + if (!JS_GetClassPrototype(cx, JSProto_Object, protop)) + return false; + } + return JS_WrapObject(cx, protop); + } + + static bool getBuiltinClass(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance, + js::ESClass* cls) { + *cls = js::ESClass::Other; + return true; + } + + static const char* className(JSContext* cx, JS::HandleObject wrapper, const js::Wrapper& baseInstance) { + return "Opaque"; + } + + virtual void preserveWrapper(JSObject* target) override { } + + virtual JSObject* createHolder(JSContext* cx, JSObject* wrapper) override + { + return JS_NewObjectWithGivenProto(cx, nullptr, nullptr); + } + + static OpaqueXrayTraits singleton; +}; + +XrayType GetXrayType(JSObject* obj); +XrayTraits* GetXrayTraits(JSObject* obj); + +// NB: Base *must* derive from JSProxyHandler +template +class XrayWrapper : public Base { + public: + constexpr explicit XrayWrapper(unsigned flags) + : Base(flags | WrapperFactory::IS_XRAY_WRAPPER_FLAG, Traits::HasPrototype) + { }; + + /* Standard internal methods. */ + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle wrapper, JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool defineProperty(JSContext* cx, JS::Handle wrapper, JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult& result) const override; + virtual bool ownPropertyKeys(JSContext* cx, JS::Handle wrapper, + JS::AutoIdVector& props) const override; + virtual bool delete_(JSContext* cx, JS::Handle wrapper, + JS::Handle id, JS::ObjectOpResult& result) const override; + virtual bool enumerate(JSContext* cx, JS::Handle wrapper, + JS::MutableHandle objp) const override; + virtual bool getPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::MutableHandleObject protop) const override; + virtual bool setPrototype(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject proto, JS::ObjectOpResult& result) const override; + virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::HandleObject wrapper, bool* isOrdinary, + JS::MutableHandleObject protop) const override; + virtual bool setImmutablePrototype(JSContext* cx, JS::HandleObject wrapper, + bool* succeeded) const override; + virtual bool preventExtensions(JSContext* cx, JS::Handle wrapper, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext* cx, JS::Handle wrapper, bool* extensible) const override; + virtual bool has(JSContext* cx, JS::Handle wrapper, JS::Handle id, + bool* bp) const override; + virtual bool get(JSContext* cx, JS::Handle wrapper, JS::HandleValue receiver, + JS::Handle id, JS::MutableHandle vp) const override; + virtual bool set(JSContext* cx, JS::Handle wrapper, JS::Handle id, + JS::Handle v, JS::Handle receiver, + JS::ObjectOpResult& result) const override; + virtual bool call(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + virtual bool construct(JSContext* cx, JS::Handle wrapper, + const JS::CallArgs& args) const override; + + /* SpiderMonkey extensions. */ + virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle wrapper, JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool hasOwn(JSContext* cx, JS::Handle wrapper, JS::Handle id, + bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle wrapper, + JS::AutoIdVector& props) const override; + + virtual bool getBuiltinClass(JSContext* cx, JS::HandleObject wapper, js::ESClass* cls) const override; + virtual const char* className(JSContext* cx, JS::HandleObject proxy) const override; + + static const XrayWrapper singleton; + + private: + template + typename mozilla::EnableIf::Type + getPrototypeHelper(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, JS::MutableHandleObject protop) const + { + return Traits::singleton.getPrototype(cx, wrapper, target, protop); + } + template + typename mozilla::EnableIf::Type + getPrototypeHelper(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, JS::MutableHandleObject protop) const + { + return Base::getPrototype(cx, wrapper, protop); + } + bool getPrototypeHelper(JSContext* cx, JS::HandleObject wrapper, + JS::HandleObject target, JS::MutableHandleObject protop) const + { + return getPrototypeHelper(cx, wrapper, target, + protop); + } + + protected: + bool getPropertyKeys(JSContext* cx, JS::Handle wrapper, unsigned flags, + JS::AutoIdVector& props) const; +}; + +#define PermissiveXrayXPCWN xpc::XrayWrapper +#define SecurityXrayXPCWN xpc::XrayWrapper +#define PermissiveXrayDOM xpc::XrayWrapper +#define SecurityXrayDOM xpc::XrayWrapper +#define PermissiveXrayJS xpc::XrayWrapper +#define PermissiveXrayOpaque xpc::XrayWrapper + +extern template class PermissiveXrayXPCWN; +extern template class SecurityXrayXPCWN; +extern template class PermissiveXrayDOM; +extern template class SecurityXrayDOM; +extern template class PermissiveXrayJS; +extern template class PermissiveXrayOpaque; +extern template class PermissiveXrayXPCWN; + +class SandboxProxyHandler : public js::Wrapper { +public: + constexpr SandboxProxyHandler() : js::Wrapper(0) + { + } + + virtual bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) const override; + + // We just forward the high-level methods to the BaseProxyHandler versions + // which implement them in terms of lower-level methods. + virtual bool has(JSContext* cx, JS::Handle proxy, JS::Handle id, + bool* bp) const override; + virtual bool get(JSContext* cx, JS::Handle proxy, JS::HandleValue receiver, + JS::Handle id, JS::MutableHandle vp) const override; + virtual bool set(JSContext* cx, JS::Handle proxy, JS::Handle id, + JS::Handle v, JS::Handle receiver, + JS::ObjectOpResult& result) const override; + + virtual bool getPropertyDescriptor(JSContext* cx, JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) const override; + virtual bool hasOwn(JSContext* cx, JS::Handle proxy, JS::Handle id, + bool* bp) const override; + virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle proxy, + JS::AutoIdVector& props) const override; + virtual bool enumerate(JSContext* cx, JS::Handle proxy, + JS::MutableHandle objp) const override; +}; + +extern const SandboxProxyHandler sandboxProxyHandler; + +// A proxy handler that lets us wrap callables and invoke them with +// the correct this object, while forwarding all other operations down +// to them directly. +class SandboxCallableProxyHandler : public js::Wrapper { +public: + constexpr SandboxCallableProxyHandler() : js::Wrapper(0) + { + } + + virtual bool call(JSContext* cx, JS::Handle proxy, + const JS::CallArgs& args) const override; + + static const size_t SandboxProxySlot = 0; + + static inline JSObject* getSandboxProxy(JS::Handle proxy) + { + return &js::GetProxyExtra(proxy, SandboxProxySlot).toObject(); + } +}; + +extern const SandboxCallableProxyHandler sandboxCallableProxyHandler; + +class AutoSetWrapperNotShadowing; + +/* + * Slots for Xray expando objects. See comments in XrayWrapper.cpp for details + * of how these get used; we mostly want the value of JSSLOT_EXPANDO_COUNT here. + */ +enum ExpandoSlots { + JSSLOT_EXPANDO_NEXT = 0, + JSSLOT_EXPANDO_ORIGIN, + JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL, + JSSLOT_EXPANDO_PROTOTYPE, + JSSLOT_EXPANDO_COUNT +}; + +extern const JSClassOps XrayExpandoObjectClassOps; + +/* + * Clear the given slot on all Xray expandos for the given object. + * + * No-op when called on non-main threads (where Xrays don't exist). + */ +void +ClearXrayExpandoSlots(JSObject* target, size_t slotIndex); + +/* + * Ensure the given wrapper has an expando object and return it. This can + * return null on failure. Will only be called when "wrapper" is an Xray for a + * DOM object. + */ +JSObject* +EnsureXrayExpandoObject(JSContext* cx, JS::HandleObject wrapper); + +} // namespace xpc + +#endif diff --git a/js/xpconnect/wrappers/moz.build b/js/xpconnect/wrappers/moz.build new file mode 100644 index 000000000..21feb7c7e --- /dev/null +++ b/js/xpconnect/wrappers/moz.build @@ -0,0 +1,41 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +EXPORTS += [ + 'WrapperFactory.h', +] + +UNIFIED_SOURCES += [ + 'AccessCheck.cpp', + 'AddonWrapper.cpp', + 'ChromeObjectWrapper.cpp', + 'FilteringWrapper.cpp', + 'WaiveXrayWrapper.cpp', + 'WrapperFactory.cpp', +] + +# XrayWrapper needs to be built separately becaue of template instantiations. +SOURCES += [ + 'XrayWrapper.cpp', +] + +# warning C4661 for FilteringWrapper +if CONFIG['_MSC_VER']: + CXXFLAGS += [ + '-wd4661', # no suitable definition provided for explicit template instantiation request + ] + +include('/ipc/chromium/chromium-config.mozbuild') + +FINAL_LIBRARY = 'xul' + +LOCAL_INCLUDES += [ + '../../../dom/base', + '../src', +] + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-shadow'] -- cgit v1.2.3