From ba38997a10b05c02e54f288d5e3b84a590a8ad5b Mon Sep 17 00:00:00 2001 From: Ted Campbell Date: Wed, 1 Mar 2017 21:35:39 -0500 Subject: [PATCH 01/61] Bug 1338920 - Support JSOP_SPREADCALLARRAY in Ion r=h4writer MozReview-Commit-ID: 8FQILAzOVmO --HG-- extra : rebase_source : 5bd1b774d5b27a44f96d359f6dadadde0271a027 --- js/src/jit/IonBuilder.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 8471ee491045..e065c777c3e8 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1961,6 +1961,7 @@ IonBuilder::inspectOpcode(JSOp op) return jsop_newobject(); case JSOP_NEWARRAY: + case JSOP_SPREADCALLARRAY: return jsop_newarray(GET_UINT32(pc)); case JSOP_NEWARRAY_COPYONWRITE: From 5d801091a8bc53bd3ead47a9830babea01231ad1 Mon Sep 17 00:00:00 2001 From: Ted Campbell Date: Tue, 21 Mar 2017 18:38:54 -0400 Subject: [PATCH 02/61] Bug 1338920 - Support JSOP_SPREADCALL in Ion r=h4writer MozReview-Commit-ID: 1WOhrGAedLi --HG-- extra : rebase_source : ac85b29323641df42d5160d1efcf74a9af6f7e72 --- js/src/jit/IonBuilder.cpp | 42 +++++++++++++++++++++++++++++++++++++++ js/src/jit/IonBuilder.h | 1 + 2 files changed, 43 insertions(+) diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index e065c777c3e8..505b9ba04698 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -2013,6 +2013,9 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_FUNAPPLY: return jsop_funapply(GET_ARGC(pc)); + case JSOP_SPREADCALL: + return jsop_spreadcall(); + case JSOP_CALL: case JSOP_CALL_IGNORES_RV: case JSOP_CALLITER: @@ -5051,6 +5054,45 @@ IonBuilder::jsop_funapply(uint32_t argc) return jsop_funapplyarguments(argc); } +AbortReasonOr +IonBuilder::jsop_spreadcall() +{ + // The arguments array is constructed by a JSOP_SPREADCALLARRAY and not + // leaked to user. The complications of spread call iterator behaviour are + // handled when the user objects are expanded and copied into this hidden + // array. + +#ifdef DEBUG + // If we know class, ensure it is what we expected + MDefinition* argument = current->peek(-1); + if (TemporaryTypeSet* objTypes = argument->resultTypeSet()) + if (const Class* clasp = objTypes->getKnownClass(constraints())) + MOZ_ASSERT(clasp == &ArrayObject::class_); +#endif + + MDefinition* argArr = current->pop(); + MDefinition* argThis = current->pop(); + MDefinition* argFunc = current->pop(); + + // Extract call target. + TemporaryTypeSet* funTypes = argFunc->resultTypeSet(); + JSFunction* target = getSingleCallTarget(funTypes); + WrappedFunction* wrappedTarget = target ? new(alloc()) WrappedFunction(target) : nullptr; + + // Dense elements of argument array + MElements* elements = MElements::New(alloc(), argArr); + current->add(elements); + + MApplyArray* apply = MApplyArray::New(alloc(), wrappedTarget, argFunc, elements, argThis); + current->add(apply); + current->push(apply); + MOZ_TRY(resumeAfter(apply)); + + // TypeBarrier the call result + TemporaryTypeSet* types = bytecodeTypes(pc); + return pushTypeBarrier(apply, types, BarrierKind::TypeSet); +} + AbortReasonOr IonBuilder::jsop_funapplyarray(uint32_t argc) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 1ed3bdce9230..b8f2c2475265 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -504,6 +504,7 @@ class IonBuilder AbortReasonOr jsop_funapply(uint32_t argc); AbortReasonOr jsop_funapplyarguments(uint32_t argc); AbortReasonOr jsop_funapplyarray(uint32_t argc); + AbortReasonOr jsop_spreadcall(); AbortReasonOr jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue); AbortReasonOr jsop_eval(uint32_t argc); AbortReasonOr jsop_label(); From bbe464023537529dad3fbd4f81b74b44a0715328 Mon Sep 17 00:00:00 2001 From: Anthony Ramine Date: Mon, 3 Apr 2017 12:07:27 -0500 Subject: [PATCH 03/61] servo: Merge #16245 - Don't share the SSL client between the private and public groups (from nox:ssl); r=avadacatavra Source-Repo: https://github.com/servo/servo Source-Revision: e8ed3e0b7f5fefffa7fa8c73846bdcb6673bf36e --HG-- extra : subtree_source : https%3A//hg.mozilla.org/projects/converted-servo-linear extra : subtree_revision : f91c702f9d6f5406ce38c4d212204452fcfdfb00 --- servo/components/net/resource_thread.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/servo/components/net/resource_thread.rs b/servo/components/net/resource_thread.rs index 987f085855bb..b54377563a14 100644 --- a/servo/components/net/resource_thread.rs +++ b/servo/components/net/resource_thread.rs @@ -114,10 +114,11 @@ fn create_resource_groups(config_dir: Option<&Path>) ssl_client: ssl_client.clone(), connector: create_http_connector(ssl_client.clone()), }; + let private_ssl_client = create_ssl_client("certs"); let private_resource_group = ResourceGroup { http_state: Arc::new(HttpState::new()), - ssl_client: ssl_client.clone(), - connector: create_http_connector(ssl_client), + ssl_client: private_ssl_client.clone(), + connector: create_http_connector(private_ssl_client), }; (resource_group, private_resource_group) } From ee8d9054fb843222490dfcbed1493594fce80dad Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Mon, 3 Apr 2017 17:46:47 +0100 Subject: [PATCH 04/61] Bug 1353041 - fix Safari import of folders, r=mossop MozReview-Commit-ID: Jw0lmIi5aZK --HG-- extra : rebase_source : b3038740fad3e6fe2e53b356458fa5541c1d50bb --- .../migration/SafariProfileMigrator.js | 2 +- .../tests/unit/Library/Safari/Bookmarks.plist | Bin 1860 -> 1956 bytes .../tests/unit/test_Safari_bookmarks.js | 5 +++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/browser/components/migration/SafariProfileMigrator.js b/browser/components/migration/SafariProfileMigrator.js index 628e4305d9b3..1a20b9354740 100644 --- a/browser/components/migration/SafariProfileMigrator.js +++ b/browser/components/migration/SafariProfileMigrator.js @@ -179,7 +179,7 @@ Bookmarks.prototype = { return { url, title }; } return null; - }).filter(e => !!e); + }, this).filter(e => !!e); }, }; diff --git a/browser/components/migration/tests/unit/Library/Safari/Bookmarks.plist b/browser/components/migration/tests/unit/Library/Safari/Bookmarks.plist index 40783c7b150109eed37a7f4a6a007488caa8577e..8046d5e9c984ed9f908d748d813a383b0d3c00ab 100644 GIT binary patch literal 1956 zcmZvcO^g&p6vzA9ML|YBWk4aYKtyV{KdQT{LBy)AE?5Lym!16pma%uHcemZynPIkJ z-J6Mt9yFlFuY(7p7(9@8^<*@hOz^M)G@eXMNDK+a=*8&8H`B9Z{g}gas#5iP@BQEZ z)nv1l&S$21TUJy}Hy+3iUMLsVjwX|=(U_f!TeGFo03Qoy(pt5Z)Jwx@R!jbQV0f_! zJDXNq5os+MNm}!1qh2ob(umuXnI7_d$`R(SKmiRY3Y{<#5jE|In^$@se(LZoSyNX% z_|WP#<-*1lw1H|;&(f(hX#q@N&$ns7kjotpVd^*lvpJ$Tu(4+c9xPoJn%1X}1Nmg5ermB^87~bTKb5s$bD}gfz|%?wD#Wct2;w*n zH~la|f_jjt2E~N%Z2s-D)x91Pc3bGtdzA|Rif>adD7c=>@4nrJS?0urR56DFLuH$hN zVGFwm3u+@0MI5;Sr6I8?@mRY;Aa7bpUW{iNjV}1f(s+B7b5WmJwAvX5j>Ryf=rimC zm$-;p5&Th?ThKUYzLRr1A}wb;30>%7?O_IsoQhq)NZM4>VjjjJqLIlE#ymhQ5I%we zn8k_9ZEWULyfWMAMq|1w{^3e31#=#nkEh~Rx~DP|*Xv2`&|F+8PtGlE3r3D~&YoSK zrFj;EiT~3uaL-Lu)($;=aN_>S6GzW;@}Ohn zXXt#}$hO17FyI!lLkBv*1k}_XHk-gzD_=uTNC_Lgh9uII-zCb|H-)M-g_xMSkc*8$1ifi3!KGTvvqMGCJ~f11%=CX19Xb;c-~x(7AfEcpjjkNnH|P zf*jw2H`4^o18xhLedfD1^+VwXxi(u@7zUokW?a3P&Nr5&Z=D&2J(};8PGuLSrUnbL z)FZ8zc1wK{mV{K6UX@;#-jS|JH>JDMZ_=N#D({m``Jg-~pO$Cj%ktauRrxddEBQP5 z2l+?&j{K{#St%-86;~n3Vda=|UdfaT$|dD3<$dL2_6z&Crl$YL<3F8`Mr~iS~weS$k8vrhTq`p5(q2|6c#eSY@m>))>9UUSq%E8|RGJm^5BC ZUNJ5k?;7t(ou4A-U+H4^+x-|H{0+Vp9ti*d literal 1860 zcmZXU%WoV-5QlqSiy=TZug%8cVMus|v^|gRndv|t-80jP6B|O}_1bx@*6Xo7c)YV( zPcY5_A+DSd65Mj(0zwfHLJC3R5RRO%$REHBIKmMjLCx4ko3JZQch&ZMTfeI6n(L*> zYHphM6;;!XO7n87y0aP2i7Z=QY4?{Kjqz|MSW43Qe%yWVr>XT`+^W_F4$CBsr{n%= zlEKl#!eKVC9FB?S!^3tg?Ay%4fh|cuLctiLpFQ&A%zZ^0di1d!k3Z3vO7awt`v#EX z^Kmy%7LvFRL?#8bWF#?Rp@oU*Q_LBdq3ttayCLJ2-Ky>#c)f`6d_Q}4{Zi1+x>=en zChg`K5S;bbLGh5bvpU@ba%2*ZMJ&RW7lfElYGda54i+LXrOSL~N%z*S;b#xOGV1up z#p#K+?kl^sJ$v`n_RlshOkB+SNq6yTJsicp+kI#_ux{V_g0UMjjgS%jjm)jk~D3PX8pynBp+*4_dKL#B5p5i)DJZBB3D@z zql+j<32LJil%WySL46d1)kX8AeI0fqXdIq7`1`QNup5EX30g!cJhSqRk&(UNodoL! z_)_?`ON9rYsyz)#Tw8|RG6fD@5ny65iCH8BjsnXKO^*k{^jp<>p=3))yVpzO(N4Bf z$k?bq)8K`pWR5&nd%nmH6P$+@!;+gO7?29`GUGp zKQx^Lg;gS7c(L{pAUzRCQ&Nd-?%L2Go`bm?dPV93LYQY#NuWWtkx4$f-d@VGP2kIo z*}^D!M_#QREkd-Za9ql87)c0GN*nvq^{}*D#{(#{Bi^bG56GT_J}6b6>~xMN`9>X2 z32>FrF|%eBxr|I2@qlCM*wD%$@Hn&uq(OuMCDIc`S3ID5G0PSqn`I{L>C>fEA_=Xz zfMkx(p*}8L2PxooxIPvx4I!Vzak)(c;d`y>{;fDc_FA%>^y2xXY_yHKH{0BHlvsAG z_F928J!aY@z>$yrzNtH7br9Iv)v1i6>X8~(R#3U90jt^st z*inSJfQT&u(HPL(M1N0&K z2z`NmLcgLv(O-(D98r9QE9aD1<$dL*@{Mvw`Cc7X52SY1-P>Y92}y`z4o-cx^6 z|ImiC9ojB!SmW9?t*tF+nf8hHmG-rESNlQxUDx!AUeyoir}WGEHGNU<>7VMi^xOKk z`aS(OLo+JIPNQy|G@8aGqhqAT$HwQzm&R@5uJMcUxA9M9N2OjFt 0 && aTitle == label) { let index = expectedParents.indexOf(aParentId); Assert.ok(index != -1, "Found expected parent"); @@ -40,6 +44,7 @@ add_task(function* () { // Check the bookmarks have been imported to all the expected parents. Assert.ok(!expectedParents.length, "No more expected parents"); + Assert.ok(gotFolder, "Should have seen the folder get imported"); Assert.equal(itemCount, 13, "Should import all 13 items."); // Check that the telemetry matches: Assert.equal(MigrationUtils._importQuantities.bookmarks, itemCount, "Telemetry reporting correct."); From 8a8722824e9442b9ca4ad2ee899ba11c5c80887b Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Mon, 3 Apr 2017 11:47:22 +0100 Subject: [PATCH 05/61] Bug 1352513 - re-add the hidden window exception behind a pref, r=bholley MozReview-Commit-ID: 3q1CZ5QCuus --HG-- extra : rebase_source : adb93e2ee26e17f8ce03023deebc85d657dfe498 --- caps/nsScriptSecurityManager.cpp | 17 +++++++++++++++++ modules/libpref/init/all.js | 4 ++++ 2 files changed, 21 insertions(+) diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 59c24902b55d..9db0ee2a7f25 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -898,6 +898,23 @@ nsScriptSecurityManager::CheckLoadURIFlags(nsIURI *aSourceURI, } } + static bool sCanLoadChromeInContent = false; + static bool sCachedCanLoadChromeInContentPref = false; + if (!sCachedCanLoadChromeInContentPref) { + sCachedCanLoadChromeInContentPref = true; + mozilla::Preferences::AddBoolVarCache(&sCanLoadChromeInContent, + "security.allow_chrome_frames_inside_content"); + } + if (sCanLoadChromeInContent) { + // Special-case the hidden window: it's allowed to load + // URI_IS_UI_RESOURCE no matter what. Bug 1145470 tracks removing this. + nsAutoCString sourceSpec; + if (NS_SUCCEEDED(aSourceBaseURI->GetSpec(sourceSpec)) && + sourceSpec.EqualsLiteral("resource://gre-resources/hiddenWindow.html")) { + return NS_OK; + } + } + if (reportErrors) { ReportError(nullptr, errorTag, aSourceURI, aTargetURI); } diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index bc18ed0f660f..f6339c91023c 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2365,6 +2365,10 @@ pref("security.cert_pinning.process_headers_from_non_builtin_roots", false); // their protocol with the inner URI of the view-source URI pref("security.view-source.reachable-from-inner-protocol", false); +// If set to true, in some limited circumstances it may be possible to load +// privileged content in frames inside unprivileged content. +pref("security.allow_chrome_frames_inside_content", false); + // Services security settings pref("services.settings.server", "https://firefox.settings.services.mozilla.com/v1"); From fad6b795cc317bc30a18266dae7b54eac942fbe4 Mon Sep 17 00:00:00 2001 From: Gijs Kruitbosch Date: Mon, 3 Apr 2017 14:31:00 +0100 Subject: [PATCH 06/61] Bug 1351991 - use a chrome-privileged frame to test our CSS files, r=florian MozReview-Commit-ID: EQIs7bis5Ag --HG-- extra : rebase_source : e964a3164b768ce65bd7e652cf73f6035555b8b3 --- .../test/static/browser_parsable_css.js | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/browser/base/content/test/static/browser_parsable_css.js b/browser/base/content/test/static/browser_parsable_css.js index 02279fd61b0d..6a2e4513de79 100644 --- a/browser/base/content/test/static/browser_parsable_css.js +++ b/browser/base/content/test/static/browser_parsable_css.js @@ -42,6 +42,21 @@ let whitelist = [ {sourceName: /res\/forms\.css$/i, errorMessage: /Unknown property.*overflow-clip-box/i, isFromDevTools: false}, + // These variables are declared somewhere else, and error when we load the + // files directly. They're all marked intermittent because their appearance + // in the error console seems to not be consistent. + {sourceName: /jsonview\/css\/general\.css$/i, + intermittent: true, + errorMessage: /Property contained reference to invalid variable.*color/i, + isFromDevTools: true}, + {sourceName: /webide\/skin\/logs\.css$/i, + intermittent: true, + errorMessage: /Property contained reference to invalid variable.*color/i, + isFromDevTools: true}, + {sourceName: /devtools\/skin\/animationinspector\.css$/i, + intermittent: true, + errorMessage: /Property contained reference to invalid variable.*color/i, + isFromDevTools: true}, ]; if (!Services.prefs.getBoolPref("full-screen-api.unprefix.enabled")) { @@ -93,16 +108,6 @@ function ignoredError(aErrorObject) { return false; } -function once(target, name) { - return new Promise((resolve, reject) => { - let cb = () => { - target.removeEventListener(name, cb); - resolve(); - }; - target.addEventListener(name, cb); - }); -} - var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"] .getService(Ci.nsIChromeRegistry); var gChromeMap = new Map(); @@ -235,10 +240,12 @@ add_task(function* checkAllTheCSS() { // Create a clean iframe to load all the files into. This needs to live at a // chrome URI so that it's allowed to load and parse any styles. let testFile = getRootDirectory(gTestPath) + "dummy_page.html"; - let windowless = Services.appShell.createWindowlessBrowser(); - let iframe = windowless.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe"); - windowless.document.documentElement.appendChild(iframe); - let iframeLoaded = once(iframe, "load"); + let HiddenFrame = Cu.import("resource:///modules/HiddenFrame.jsm", {}).HiddenFrame; + let hiddenFrame = new HiddenFrame(); + let win = yield hiddenFrame.get(); + let iframe = win.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe"); + win.document.documentElement.appendChild(iframe); + let iframeLoaded = BrowserTestUtils.waitForEvent(iframe, "load", true); iframe.contentWindow.location = testFile; yield iframeLoaded; let doc = iframe.contentWindow.document; @@ -322,7 +329,7 @@ add_task(function* checkAllTheCSS() { // Confirm that all whitelist rules have been used. for (let item of whitelist) { - if (!item.used && isDevtools == item.isFromDevTools) { + if (!item.used && isDevtools == item.isFromDevTools && !item.intermittent) { ok(false, "Unused whitelist item. " + (item.sourceName ? " sourceName: " + item.sourceName : "") + (item.errorMessage ? " errorMessage: " + item.errorMessage : "")); @@ -344,7 +351,8 @@ add_task(function* checkAllTheCSS() { doc.head.innerHTML = ""; doc = null; iframe = null; - windowless.close(); - windowless = null; + win = null; + hiddenFrame.destroy(); + hiddenFrame = null; imageURIsToReferencesMap = null; }); From 77f022ac76d0cd7bc93a491dedb67825a390cc85 Mon Sep 17 00:00:00 2001 From: Florian Queze Date: Thu, 30 Mar 2017 11:32:20 +0200 Subject: [PATCH 07/61] Bug 1351991 - use resource: URIs to test CSS files when possible, r=Gijs MozReview-Commit-ID: JgEhCAhLjOM --HG-- extra : rebase_source : f25c2f0d2df3b2d5a777cf75d0105fcd65ea24f4 --- .../test/static/browser_parsable_css.js | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/browser/base/content/test/static/browser_parsable_css.js b/browser/base/content/test/static/browser_parsable_css.js index 6a2e4513de79..21178109e9c0 100644 --- a/browser/base/content/test/static/browser_parsable_css.js +++ b/browser/base/content/test/static/browser_parsable_css.js @@ -39,7 +39,7 @@ let whitelist = [ errorMessage: /Unknown property.*-moz-/i, isFromDevTools: false}, // Reserved to UA sheets unless layout.css.overflow-clip-box.enabled flipped to true. - {sourceName: /res\/forms\.css$/i, + {sourceName: /(?:res|gre-resources)\/forms\.css$/i, errorMessage: /Unknown property.*overflow-clip-box/i, isFromDevTools: false}, // These variables are declared somewhere else, and error when we load the @@ -112,6 +112,16 @@ var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"] .getService(Ci.nsIChromeRegistry); var gChromeMap = new Map(); +var resHandler = Services.io.getProtocolHandler("resource") + .QueryInterface(Ci.nsIResProtocolHandler); +var gResourceMap = []; +function trackResourcePrefix(prefix) { + let uri = Services.io.newURI("resource://" + prefix + "/"); + gResourceMap.unshift([prefix, resHandler.resolveURI(uri)]); +} +trackResourcePrefix("gre"); +trackResourcePrefix("app"); + function getBaseUriForChromeUri(chromeUri) { let chromeFile = chromeUri + "gobbledygooknonexistentfile.reallynothere"; let uri = Services.io.newURI(chromeFile); @@ -123,35 +133,34 @@ function parseManifest(manifestUri) { return fetchFile(manifestUri.spec).then(data => { for (let line of data.split("\n")) { let [type, ...argv] = line.split(/\s+/); - let component; if (type == "content" || type == "skin") { - [component] = argv; - } else { - // skip unrelated lines - continue; + let chromeUri = `chrome://${argv[0]}/${type}/`; + gChromeMap.set(getBaseUriForChromeUri(chromeUri), chromeUri); + } else if (type == "resource") { + trackResourcePrefix(argv[0]); } - let chromeUri = `chrome://${component}/${type}/`; - gChromeMap.set(getBaseUriForChromeUri(chromeUri), chromeUri); } }); } -function convertToChromeUri(fileUri) { - let baseUri = fileUri.spec; +function convertToCodeURI(fileUri) { + let baseUri = fileUri; let path = ""; while (true) { let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2); - if (slashPos < 0) { - info(`File not accessible from chrome protocol: ${fileUri.path}`); + if (slashPos <= 0) { + // File not accessible from chrome protocol, try resource:// + for (let res of gResourceMap) { + if (fileUri.startsWith(res[1])) + return fileUri.replace(res[1], "resource://" + res[0] + "/"); + } + // Give up and return the original URL. return fileUri; } path = baseUri.slice(slashPos + 1) + path; baseUri = baseUri.slice(0, slashPos + 1); - if (gChromeMap.has(baseUri)) { - let chromeBaseUri = gChromeMap.get(baseUri); - let chromeUri = `${chromeBaseUri}${path}`; - return Services.io.newURI(chromeUri); - } + if (gChromeMap.has(baseUri)) + return gChromeMap.get(baseUri) + path; } } @@ -292,8 +301,8 @@ add_task(function* checkAllTheCSS() { linkEl.addEventListener("load", onLoad); linkEl.addEventListener("error", onError); linkEl.setAttribute("type", "text/css"); - let chromeUri = convertToChromeUri(uri); - linkEl.setAttribute("href", chromeUri.spec + kPathSuffix); + let chromeUri = convertToCodeURI(uri.spec); + linkEl.setAttribute("href", chromeUri + kPathSuffix); })); doc.head.appendChild(linkEl); } From a16d4e898bcbd0befffa8566b731c55a9c3b45e9 Mon Sep 17 00:00:00 2001 From: manotejmeka Date: Mon, 3 Apr 2017 14:02:01 -0400 Subject: [PATCH 08/61] Bug 1335905 - Add Preferences search feature, preffed off by default. r=jaws,mconley Code written by Manotej Meka and Ian Ferguson This is the initial landing of the search feature, and is preffed off behind browser.preferences.search. MozReview-Commit-ID: 7iaeRsIIV3Y --HG-- extra : rebase_source : 4444caea3622bcd2ff4ca49d23fa8b609e724146 --- browser/app/profile/firefox.js | 4 + .../preferences/in-content/findInPage.js | 308 ++++++++++++++++++ .../components/preferences/in-content/jar.mn | 1 + .../preferences/in-content/preferences.js | 5 +- .../preferences/in-content/preferences.xul | 17 +- .../preferences/in-content/searchResults.xul | 18 + .../preferences/in-content/tests/browser.ini | 1 + .../browser_search_within_preferences.js | 172 ++++++++++ .../browser/preferences/preferences.dtd | 3 + .../preferences/preferences.properties | 6 + .../themes/shared/incontentprefs/icons.svg | 10 + .../shared/incontentprefs/preferences.inc.css | 4 + 12 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 browser/components/preferences/in-content/findInPage.js create mode 100644 browser/components/preferences/in-content/searchResults.xul create mode 100644 browser/components/preferences/in-content/tests/browser_search_within_preferences.js diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index ecf638ba9dfa..692777603787 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -670,6 +670,10 @@ pref("browser.preferences.instantApply", false); #else pref("browser.preferences.instantApply", true); #endif + +// Toggling Search bar on and off in about:preferences +pref("browser.preferences.search", false); + // Once the Storage Management is completed. // (The Storage Management-related prefs are browser.storageManager.* ) // The Offline(Appcache) Group section in about:preferences will be hidden. diff --git a/browser/components/preferences/in-content/findInPage.js b/browser/components/preferences/in-content/findInPage.js new file mode 100644 index 000000000000..c18e24ec7d81 --- /dev/null +++ b/browser/components/preferences/in-content/findInPage.js @@ -0,0 +1,308 @@ +/* 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/. */ + +/* import-globals-from preferences.js */ + +var gSearchResultsPane = { + findSelection: null, + searchResultsCategory: null, + searchInput: null, + + init() { + let controller = this.getSelectionController(); + this.findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND); + this.searchResultsCategory = document.getElementById("category-search-results"); + + this.searchInput = document.getElementById("searchInput"); + this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search"); + if (!this.searchInput.hidden) { + this.searchInput.addEventListener("command", this); + this.searchInput.addEventListener("focus", this); + } + }, + + handleEvent(event) { + if (event.type === "command") { + this.searchFunction(event); + } else if (event.type === "focus") { + this.initializeCategories(); + } + }, + + /** + * Check that the passed string matches the filter arguments. + * + * @param String str + * to search for filter words in. + * @param String filter + * is a string containing all of the words to filter on. + * @returns boolean + * true when match in string else false + */ + stringMatchesFilters(str, filter) { + if (!filter || !str) { + return true; + } + let searchStr = str.toLowerCase(); + let filterStrings = filter.toLowerCase().split(/\s+/); + return !filterStrings.some(f => searchStr.indexOf(f) == -1); + }, + + categoriesInitialized: false, + + /** + * Will attempt to initialize all uninitialized categories + */ + initializeCategories() { + // Initializing all the JS for all the tabs + if (!this.categoriesInitialized) { + this.categoriesInitialized = true; + // Each element of gCategoryInits is a name + for (let [/* name */, category] of gCategoryInits) { + if (!category.inited) { + category.init(); + } + } + } + }, + + /** + * Finds and returns text nodes within node and all descendants + * Iterates through all the sibilings of the node object and adds the sibilings + * to an array if sibiling is a TEXT_NODE else checks the text nodes with in current node + * Source - http://stackoverflow.com/questions/10730309/find-all-text-nodes-in-html-page + * + * @param Node nodeObject + * DOM element + * @returns array of text nodes + */ + textNodeDescendants(node) { + if (!node) { + return []; + } + let all = []; + for (node = node.firstChild; node; node = node.nextSibling) { + if (node.nodeType === node.TEXT_NODE) { + all.push(node); + } else { + all = all.concat(this.textNodeDescendants(node)); + } + } + return all; + }, + + /** + * This function is used to find words contained within the text nodes. + * We pass in the textNodes because they contain the text to be highlighted. + * We pass in the nodeSizes to tell exactly where highlighting need be done. + * When creating the range for highlighting, if the nodes are section is split + * by an access key, it is important to have the size of each of the nodes summed. + * @param Array textNodes + * List of DOM elements + * @param Array nodeSizes + * Running size of text nodes. This will contain the same number of elements as textNodes. + * The first element is the size of first textNode element. + * For any nodes after, they will contain the summation of the nodes thus far in the array. + * Example: + * textNodes = [[This is ], [a], [n example]] + * nodeSizes = [[8], [9], [18]] + * This is used to determine the offset when highlighting + * @param String textSearch + * Concatination of textNodes's text content + * Example: + * textNodes = [[This is ], [a], [n example]] + * nodeSizes = "This is an example" + * This is used when executing the regular expression + * @param String searchPhrase + * word or words to search for + * @returns boolean + * Returns true when atleast one instance of search phrase is found, otherwise false + */ + highlightMatches(textNodes, nodeSizes, textSearch, searchPhrase) { + let indices = []; + let i = -1; + while ((i = textSearch.indexOf(searchPhrase, i + 1)) >= 0) { + indices.push(i); + } + + // Looping through each spot the searchPhrase is found in the concatenated string + for (let startValue of indices) { + let endValue = startValue + searchPhrase.length; + let startNode = null; + let endNode = null; + let nodeStartIndex = null; + + // Determining the start and end node to highlight from + nodeSizes.forEach(function(lengthNodes, index) { + // Determining the start node + if (!startNode && lengthNodes >= startValue) { + startNode = textNodes[index]; + nodeStartIndex = index; + // Calculating the offset when found query is not in the first node + if (index > 0) { + startValue -= nodeSizes[index - 1]; + } + } + // Determining the end node + if (!endNode && lengthNodes >= endValue) { + endNode = textNodes[index]; + // Calculating the offset when endNode is different from startNode + // or when endNode is not the first node + if (index != nodeStartIndex || index > 0 ) { + endValue -= nodeSizes[index - 1]; + } + } + }); + let range = document.createRange(); + range.setStart(startNode, startValue); + range.setEnd(endNode, endValue); + this.findSelection.addRange(range); + } + + return indices.length > 0; + }, + + getSelectionController() { + // Yuck. See bug 138068. + let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell); + + let controller = docShell.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsISelectionDisplay) + .QueryInterface(Ci.nsISelectionController); + + return controller; + }, + + get strings() { + delete this.strings; + return this.strings = document.getElementById("searchResultBundle"); + }, + + /** + * Shows or hides content according to search input + * + * @param String event + * to search for filted query in + */ + searchFunction(event) { + let query = event.target.value.trim().toLowerCase(); + this.findSelection.removeAllRanges(); + + let srHeader = document.getElementById("header-searchResults"); + + if (query) { + // Showing the Search Results Tag + gotoPref("paneSearchResults"); + + this.searchResultsCategory.hidden = false; + + let resultsFound = false; + + // Building the range for highlighted areas + let rootPreferences = document.getElementById("mainPrefPane") + let rootPreferencesChildren = rootPreferences.children; + + // Showing all the children to bind JS, Access Keys, etc + for (let i = 0; i < rootPreferences.childElementCount; i++) { + rootPreferencesChildren[i].hidden = false; + } + + // Showing or Hiding specific section depending on if words in query are found + for (let i = 0; i < rootPreferences.childElementCount; i++) { + if (rootPreferencesChildren[i].className != "header" && + rootPreferencesChildren[i].className != "no-results-message" && + this.searchWithinNode(rootPreferencesChildren[i], query)) { + rootPreferencesChildren[i].hidden = false; + resultsFound = true; + } else { + rootPreferencesChildren[i].hidden = true; + } + } + // It hides Search Results header so turning it on + srHeader.hidden = false; + + if (!resultsFound) { + let noResultsEl = document.querySelector(".no-results-message"); + noResultsEl.hidden = false; + + let strings = this.strings; + document.getElementById("sorry-message").textContent = + strings.getFormattedString("searchResults.sorryMessage", [query]); + + let brandName = document.getElementById("bundleBrand").getString("brandShortName"); + document.getElementById("need-help").innerHTML = + strings.getFormattedString("searchResults.needHelp", [brandName]); + + document.getElementById("need-help-link").setAttribute("href", getHelpLinkURL("search")); + } + } else { + this.searchResultsCategory.hidden = true; + document.getElementById("sorry-message").textContent = ""; + // Going back to General when cleared + gotoPref("paneGeneral"); + } + }, + + /** + * Finding leaf nodes and checking their content for words to search, + * It is a recursive function + * + * @param Node nodeObject + * DOM Element + * @param String searchPhrase + * @returns boolean + * Returns true when found in at least one childNode, false otherwise + */ + searchWithinNode(nodeObject, searchPhrase) { + let matchesFound = false; + if (nodeObject.childElementCount == 0) { + let simpleTextNodes = this.textNodeDescendants(nodeObject); + + for (let node of simpleTextNodes) { + let result = this.highlightMatches([node], [node.length], node.textContent.toLowerCase(), searchPhrase); + matchesFound = matchesFound || result; + } + + // Collecting data from boxObject + let nodeSizes = []; + let allNodeText = ""; + let runningSize = 0; + let labelResult = false; + let valueResult = false; + let accessKeyTextNodes = this.textNodeDescendants(nodeObject.boxObject); + + for (let node of accessKeyTextNodes) { + runningSize += node.textContent.length; + allNodeText += node.textContent; + nodeSizes.push(runningSize); + } + + // Access key are presented + let complexTextNodesResult = this.highlightMatches(accessKeyTextNodes, nodeSizes, allNodeText.toLowerCase(), searchPhrase); + + // Searching some elements, such as xul:button, have a 'label' attribute that contains the user-visible text. + if (nodeObject.getAttribute("label")) { + labelResult = this.stringMatchesFilters(nodeObject.getAttribute("label"), searchPhrase); + } + + // Searching some elements, such as xul:label, store their user-visible text in a "value" attribute. + if (nodeObject.getAttribute("value")) { + valueResult = this.stringMatchesFilters(nodeObject.getAttribute("value"), searchPhrase); + } + + matchesFound = matchesFound || complexTextNodesResult || labelResult || valueResult; + } + + for (let i = 0; i < nodeObject.childNodes.length; i++) { + // Search only if child node is not hidden + if (!nodeObject.childNodes[i].hidden) { + let result = this.searchWithinNode(nodeObject.childNodes[i], searchPhrase); + matchesFound = matchesFound || result; + } + } + return matchesFound; + } +} diff --git a/browser/components/preferences/in-content/jar.mn b/browser/components/preferences/in-content/jar.mn index dd4999b05bbb..05235e414afc 100644 --- a/browser/components/preferences/in-content/jar.mn +++ b/browser/components/preferences/in-content/jar.mn @@ -13,3 +13,4 @@ browser.jar: content/browser/preferences/in-content/advanced.js content/browser/preferences/in-content/applications.js content/browser/preferences/in-content/sync.js + content/browser/preferences/in-content/findInPage.js diff --git a/browser/components/preferences/in-content/preferences.js b/browser/components/preferences/in-content/preferences.js index da4b502e52e3..5dbf582faf57 100644 --- a/browser/components/preferences/in-content/preferences.js +++ b/browser/components/preferences/in-content/preferences.js @@ -10,6 +10,7 @@ /* import-globals-from privacy.js */ /* import-globals-from applications.js */ /* import-globals-from sync.js */ +/* import-globals-from findInPage.js */ /* import-globals-from ../../../base/content/utilityOverlay.js */ "use strict"; @@ -59,6 +60,8 @@ function init_all() { register_module("paneAdvanced", gAdvancedPane); register_module("paneApplications", gApplicationsPane); register_module("paneSync", gSyncPane); + register_module("paneSearchResults", gSearchResultsPane); + gSearchResultsPane.init(); let categories = document.getElementById("categories"); categories.addEventListener("select", event => gotoPref(event.target.value)); @@ -135,7 +138,7 @@ function onHashChange() { function gotoPref(aCategory) { let categories = document.getElementById("categories"); - const kDefaultCategoryInternalName = categories.firstElementChild.value; + const kDefaultCategoryInternalName = "paneGeneral"; let hash = document.location.hash; let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName; category = friendlyPrefCategoryNameToInternalName(category); diff --git a/browser/components/preferences/in-content/preferences.xul b/browser/components/preferences/in-content/preferences.xul index 939a3662a197..a4cb2913a561 100644 --- a/browser/components/preferences/in-content/preferences.xul +++ b/browser/components/preferences/in-content/preferences.xul @@ -75,6 +75,7 @@ src="chrome://browser/content/utilityOverlay.js"/> diff --git a/editor/libeditor/tests/test_bug636465.html b/editor/libeditor/tests/test_bug636465.html index 37ceebe5a2f2..6a884582e867 100644 --- a/editor/libeditor/tests/test_bug636465.html +++ b/editor/libeditor/tests/test_bug636465.html @@ -11,8 +11,8 @@ SimpleTest.waitForExplicitFinish(); function runTest() { - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", - window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); var x = document.getElementById("x"); x.focus(); onSpellCheck(x, function () { diff --git a/extensions/spellcheck/tests/chrome/test_add_remove_dictionaries.xul b/extensions/spellcheck/tests/chrome/test_add_remove_dictionaries.xul index 661eaccff496..140fb4bd91b6 100644 --- a/extensions/spellcheck/tests/chrome/test_add_remove_dictionaries.xul +++ b/extensions/spellcheck/tests/chrome/test_add_remove_dictionaries.xul @@ -67,7 +67,8 @@ function RunTest() { ok(map.exists()); hunspell.addDirectory(map); - Components.utils.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm"); + Components.utils.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm"); onSpellCheck(textbox, function () { // test that base and map dictionaries are available diff --git a/extensions/spellcheck/tests/mochitest/test_bug1170484.html b/extensions/spellcheck/tests/mochitest/test_bug1170484.html index bdfb39f18f39..91bb0c1ad339 100644 --- a/extensions/spellcheck/tests/mochitest/test_bug1170484.html +++ b/extensions/spellcheck/tests/mochitest/test_bug1170484.html @@ -13,7 +13,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1170484 /** Test for Bug 1170484 **/ SimpleTest.waitForExplicitFinish(); - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); SimpleTest.waitForFocus(doTest, window); function doTest() { diff --git a/extensions/spellcheck/tests/mochitest/test_bug1272623.html b/extensions/spellcheck/tests/mochitest/test_bug1272623.html index b5eeeacb1977..14a0ff357556 100644 --- a/extensions/spellcheck/tests/mochitest/test_bug1272623.html +++ b/extensions/spellcheck/tests/mochitest/test_bug1272623.html @@ -54,7 +54,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1272623 } add_task(function* () { - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); // Wait for the page to be ready yield new Promise(resolve => SimpleTest.waitForFocus(() => SimpleTest.executeSoon(resolve), window)); diff --git a/layout/base/tests/bug512295-1-ref.html b/layout/base/tests/bug512295-1-ref.html index b2f8201c76a9..944cb3d87bf4 100644 --- a/layout/base/tests/bug512295-1-ref.html +++ b/layout/base/tests/bug512295-1-ref.html @@ -12,7 +12,8 @@ x var p = document.getElementById('p'); var div = p.parentNode; div.focus(); - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); onSpellCheck(div, function () { var sel = window.getSelection(); sel.removeAllRanges(); diff --git a/layout/base/tests/bug512295-1.html b/layout/base/tests/bug512295-1.html index b69974d6c13f..64984a3cd831 100644 --- a/layout/base/tests/bug512295-1.html +++ b/layout/base/tests/bug512295-1.html @@ -19,7 +19,8 @@ x sel.addRange(range); p.parentNode.focus(); - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); onSpellCheck(p.parentNode, function () { sendKey('DOWN'); // now after "1" sendKey('DOWN'); // now make sure we get to the end diff --git a/layout/base/tests/bug512295-2-ref.html b/layout/base/tests/bug512295-2-ref.html index dddf935e4a8c..90600c2df727 100644 --- a/layout/base/tests/bug512295-2-ref.html +++ b/layout/base/tests/bug512295-2-ref.html @@ -12,7 +12,8 @@ x var p = document.getElementById('p'); var div = p.parentNode; div.focus(); - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); onSpellCheck(div, function () { var sel = window.getSelection(); sel.removeAllRanges(); diff --git a/layout/base/tests/bug512295-2.html b/layout/base/tests/bug512295-2.html index 3d64c44a615c..fc8c9401058d 100644 --- a/layout/base/tests/bug512295-2.html +++ b/layout/base/tests/bug512295-2.html @@ -19,7 +19,8 @@ x sel.addRange(range); p.parentNode.focus(); - SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window); + SpecialPowers.Cu.import( + "resource://testing-common/AsyncSpellCheckTestHelper.jsm", window); onSpellCheck(p.parentNode, function () { sendKey('DOWN'); // now after "1" sendKey('DOWN'); // now below the P element diff --git a/layout/base/tests/bug923376-ref.html b/layout/base/tests/bug923376-ref.html index 3a60cf195d93..0ec11f7d0b57 100644 --- a/layout/base/tests/bug923376-ref.html +++ b/layout/base/tests/bug923376-ref.html @@ -3,8 +3,8 @@