123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625 |
- # HG changeset patch
- # User Matt A. Tobin <email@mattatobin.com>
- # Date 1659988365 0
- # Parent 721e1ab786754b9bd15a02f0a3416506594af9eb
- No Bug - Create an isolated dianostic component for the suite.
- diff --git a/suite/buoy/ATTN-CC-SUITE-PATCHERS.txt b/suite/buoy/ATTN-CC-SUITE-PATCHERS.txt
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/ATTN-CC-SUITE-PATCHERS.txt
- @@ -0,0 +1,3 @@
- +Please exclude this component from any scripted or manual upgrades to the rest
- +of the suite (unless you are specifically updating this component).
- +It's handling is a special case and should be done seperately.
- \ No newline at end of file
- diff --git a/suite/buoy/ATTN-L10N-TRANSLATORS.txt b/suite/buoy/ATTN-L10N-TRANSLATORS.txt
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/ATTN-L10N-TRANSLATORS.txt
- @@ -0,0 +1,3 @@
- +This component can be ignored and does not need to be translated. Its only
- +purpose is to faciliate SeaMonkey Reconstruction as well as specific
- +testing cases.
- \ No newline at end of file
- diff --git a/suite/buoy/ZZ-buoy-prefs.js b/suite/buoy/ZZ-buoy-prefs.js
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/ZZ-buoy-prefs.js
- @@ -0,0 +1,2 @@
- +pref("toolkit.defaultChromeURI", "chrome://buoy/content/buoy.xhtml");
- +pref("prompts.contentPromptSubDialog", false);
- diff --git a/suite/buoy/content/buoy.css b/suite/buoy/content/buoy.css
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/content/buoy.css
- @@ -0,0 +1,9 @@
- +/* -*- Mode: Java; 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/. */
- +
- +html, body {
- + height: 100%;
- +}
- +
- diff --git a/suite/buoy/content/buoy.js b/suite/buoy/content/buoy.js
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/content/buoy.js
- @@ -0,0 +1,85 @@
- +/* -*- Mode: Java; 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/. */
- +
- +// e10s
- +var { EzE10SUtils } = ChromeUtils.importESModule(
- + "resource:///modules/EzE10SUtils.sys.mjs"
- +);
- +
- +// Devtools
- +ChromeUtils.defineESModuleGetters(this, {
- + BrowserToolboxLauncher: "resource://devtools/client/framework/browser-toolbox/Launcher.sys.mjs",
- +});
- +
- +Object.defineProperty(this, "BrowserConsoleManager", {
- + get() {
- + let { loader } = ChromeUtils.importESModule("resource://devtools/shared/loader/Loader.sys.mjs");
- + return loader.require("devtools/client/webconsole/browser-console-manager").BrowserConsoleManager;
- + },
- + configurable: true,
- + enumerable: true,
- +});
- +
- +// Main functions
- +var gBuoy = {
- + homepage: "about:version",
- + toContent: function(aURL) {
- + var browser = document.getElementById("main-browser");
- + EzE10SUtils.loadURI(browser, aURL);
- + },
- + toChrome: function(inType, uri, features, args) {
- + var topWindow = Services.wm.getMostRecentWindow(inType);
- +
- + if (topWindow) {
- + topWindow.focus();
- + } else if (features) {
- + Services.ww.openWindow(null, uri, "_blank", features, args);
- + } else {
- + Services.ww.openWindow(
- + null,
- + uri,
- + "_blank",
- + "chrome,all,dialog=no,extrachrome,menubar,resizable,scrollbars," +
- + "status,location,toolbar,personalbar",
- + args
- + );
- + }
- + },
- + navHome: function() {
- + var browser = document.getElementById("main-browser");
- + EzE10SUtils.loadURI(browser, this.homepage);
- + },
- + navigation: function(aNaviCmd) {
- + var browser = document.getElementById("main-browser");
- + switch (aNaviCmd) {
- + case 'back':
- + browser.goBack();
- + break;
- + case 'forward':
- + browser.goForward();
- + break;
- + case 'reload':
- + browser.reload();
- + break;
- + case 'stop':
- + browser.stop();
- + break;
- + default:
- + gBuoy.navHome();
- + }
- + },
- + devtools: function() { BrowserToolboxLauncher.init(); },
- + quitApp: function() { Services.startup.quit(Services.startup.eAttemptQuit); },
- + startup: function() {
- + var browser = document.getElementById("main-browser");
- + EzE10SUtils.loadAboutBlank(browser);
- + gBuoy.navHome();
- + },
- +}
- +
- +// Devtools Compat
- +function openWebLinkIn(url, where, params) {
- + gBuoy.toContent(url);
- +}
- diff --git a/suite/buoy/content/buoy.xhtml b/suite/buoy/content/buoy.xhtml
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/content/buoy.xhtml
- @@ -0,0 +1,155 @@
- +<?xml version="1.0"?>
- +<!-- This Source Code Form is subject to the terms of the Mozilla Public
- + - License, v. 2.0. If a copy of the MPL was not distributed with this
- + - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
- +
- +<!-- Mozilla DocType Reference:
- + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- + xmlns:xbl="http://www.mozilla.org/xbl"
- + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- + xmlns:html="http://www.w3.org/1999/xhtml"
- + xmlns:svg="http://www.w3.org/2000/svg"
- + xmlns:em="http://www.mozilla.org/2004/em-rdf#"
- +-->
- +
- +<!DOCTYPE html>
- +
- +<html id="main-window"
- + xmlns="http://www.w3.org/1999/xhtml"
- + xmlns:html="http://www.w3.org/1999/xhtml"
- + xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
- + windowtype="buoy:main"
- + screenX="10"
- + screenY="10"
- + width="640px"
- + height="480px"
- + scrolling="false"
- + persist="screenX screenY width height sizemode">
- + <head>
- + <title>SeaMonkey Diagnostic & Testing Buoy</title>
- + <link rel="stylesheet" href="chrome://global/skin/global.css" />
- + <link rel="stylesheet" href="chrome://buoy/content/buoy.css" />
- + <script defer="defer" src="chrome://global/content/customElements.js" />
- + <script defer="defer" src="chrome://buoy/content/buoy.js" />
- + <script>
- + window.addEventListener("load", gBuoy.startup);
- + </script>
- + </head>
- + <body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
- +
- + <commandset id="mainCommandSet">
- + <command id="cmd_Exit"
- + oncommand="gBuoy.quitApp();"/>
- + <command id="cmd_DevTools"
- + oncommand="gBuoy.devtools();"/>
- +
- + <command id="cmd_Navigator"
- + oncommand="gBuoy.toChrome('navigator:browser',
- + 'chrome://navigator/content/navigator.xhtml');"/>
- + <command id="cmd_MailNews"
- + oncommand="gBuoy.toChrome('mail:3pane',
- + 'chrome://messenger/content/messenger.xhtml');"/>
- + <command id="cmd_Composer"
- + oncommand="gBuoy.toChrome('composer:html',
- + 'chrome://editor/content/editor.xhtml');"/>
- + <command id="cmd_Preferences"
- + oncommand="gBuoy.toChrome('mozilla:preferences',
- + 'chrome://communicator/content/pref/preferences.xhtml');"/>
- +
- + <command id="cmd_AboutAbout"
- + oncommand="gBuoy.toContent('about:about');"/>
- + <command id="cmd_AboutConfig"
- + oncommand="gBuoy.toContent('about:config');"/>
- + <command id="cmd_AboutSupport"
- + oncommand="gBuoy.toContent('about:support');"/>
- + <command id="cmd_AboutVersion"
- + oncommand="gBuoy.toContent('about:version');"/>
- +
- + <command id="cmd_NavHome"
- + oncommand="gBuoy.navHome();"/>
- +
- + <command id="cmd_NavBack"
- + oncommand="gBuoy.navigation('back');"/>
- + <command id="cmd_NavForward"
- + oncommand="gBuoy.navigation('forward');"/>
- + <command id="cmd_NavReload"
- + oncommand="gBuoy.navigation('reload');"/>
- + <command id="cmd_NavStop"
- + oncommand="gBuoy.navigation('stop');"/>
- +
- + <command id="cmd_SeaMonkeyHomePage"
- + oncommand="gBuoy.toContent('https://www.seamonkey-project.org/');"/>
- + <command id="cmd_GetInvolved"
- + oncommand="gBuoy.toContent('https://www.seamonkey-project.org/dev/get-involved');"/>
- +
- + </commandset>
- +
- + <vbox flex="1">
- + <toolbox id="main-toolbox" style="border-bottom: 1px solid ThreeDShadow;">
- + <menubar id="main-menubar">
- + <menu id="file-menu" label="File">
- + <menupopup id="file-popup">
- + <menuitem label="Exit" command="cmd_Exit"/>
- + </menupopup>
- + </menu>
- + <menu id="edit-menu" label="Edit">
- + <menupopup id="edit-popup">
- + <menuitem label="Configuration Editor" command="cmd_AboutConfig"/>
- + <menuitem label="Preferences" command="cmd_Preferences"/>
- + </menupopup>
- + </menu>
- + <menu id="go-menu" label="Go">
- + <menupopup id="components-popup">
- + <menuitem label="Back" command="cmd_NavBack"/>
- + <menuitem label="Forward" command="cmd_NavForward"/>
- + <menuitem label="Reload" command="cmd_NavReload"/>
- + <menuitem label="Stop" command="cmd_NavStop"/>
- + <menuitem label="Home" command="cmd_NavHome"/>
- + </menupopup>
- + </menu>
- + <menu id="components-menu" label="Components">
- + <menupopup id="components-popup">
- + <menuitem label="Browser" command="cmd_Navigator"/>
- + <menuitem label="Messenger" command="cmd_MailNews"/>
- + <menuitem label="Composer" command="cmd_Composer"/>
- + </menupopup>
- + </menu>
- + <menu id="tools-menu" label="Tools">
- + <menupopup id="tools-popup">
- + <menuitem label="About: Pages" command="cmd_AboutAbout"/>
- + <menuitem label="Developer Tools" command="cmd_DevTools"/>
- + </menupopup>
- + </menu>
- + <menu id="help-menu" label="Help">
- + <menupopup id="help-popup">
- + <menuitem label="Get Involved" command="cmd_GetInvolved"/>
- + <menuitem label="Troubleshooting Information" command="cmd_AboutSupport"/>
- + <menuitem label="About SeaMonkey" command="cmd_AboutVersion"/>
- + </menupopup>
- + </menu>
- + </menubar>
- + <toolbar id="navigation-toolbar">
- + <toolbarbutton id="back-button" label="< Back" command="cmd_NavBack"/>
- + <toolbarbutton id="forward-button" label="> Forward" command="cmd_NavForward"/>
- + <toolbarbutton id="reload-button" label="O Reload" command="cmd_NavReload"/>
- + <toolbarbutton id="stop-button" label="X Stop" command="cmd_NavStop"/>
- + <html:input id="urlbar" style="flex: 1; margin: 2px; padding: 4px;" placeholder="Enter a URL..." />
- + <toolbarbutton id="go-button" label="Go ->" oncommand="var urlbar = document.getElementById('urlbar');
- + gBuoy.toContent(urlbar.value);
- + urlbar.value = '';"/>
- + </toolbar>
- + <toolbar id="quick-links-toolbar">
- + <button label="SeaMonkey Homepage" command="cmd_SeaMonkeyHomePage"/>
- + <button label="Get Involved" command="cmd_GetInvolved"/>
- + </toolbar>
- + </toolbox>
- + <browser id="main-browser"
- + flex="1"
- + type="content"
- + primary="true"
- + maychangeremoteness="true"
- + nodefaultsrc="true" />
- + </vbox>
- + </body>
- +</html>
- +
- diff --git a/suite/buoy/jar.mn b/suite/buoy/jar.mn
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/jar.mn
- @@ -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/.
- +
- +#filter substitution
- +
- +buoy.jar:
- +% content buoy %content/buoy/ contentaccessible=yes
- + content/buoy/buoy.css (content/buoy.css)
- + content/buoy/buoy.js (content/buoy.js)
- + content/buoy/buoy.xhtml (content/buoy.xhtml)
- +
- +[localization] @AB_CD@.jar:
- + browser (moz-l10n/browser/**/*.ftl)
- + buoy (locale/**/*.ftl)
- +
- diff --git a/suite/buoy/modules/BrowserWindowTracker.sys.mjs b/suite/buoy/modules/BrowserWindowTracker.sys.mjs
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/modules/BrowserWindowTracker.sys.mjs
- @@ -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/. */
- +
- +// This module is deliberately not implemented. It only exists to keep
- +// the automated tests happy. See bug 1782621.
- diff --git a/suite/buoy/modules/CustomizableUI.sys.mjs b/suite/buoy/modules/CustomizableUI.sys.mjs
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/modules/CustomizableUI.sys.mjs
- @@ -0,0 +1,360 @@
- +/* 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 is a copy of a file with the same name in Firefox. Only the
- +// pieces we're using, and a few pieces the devtools rely on such as the
- +// constants, remain.
- +
- +const lazy = {};
- +
- +ChromeUtils.defineESModuleGetters(lazy, {
- + PanelMultiView: "resource:///modules/PanelMultiView.sys.mjs",
- +});
- +
- +/**
- + * gPanelsForWindow is a list of known panels in a window which we may need to close
- + * should command events fire which target them.
- + */
- +var gPanelsForWindow = new WeakMap();
- +
- +var CustomizableUIInternal = {
- + addPanelCloseListeners(aPanel) {
- + Services.els.addSystemEventListener(aPanel, "click", this, false);
- + Services.els.addSystemEventListener(aPanel, "keypress", this, false);
- + const win = aPanel.ownerGlobal;
- + if (!gPanelsForWindow.has(win)) {
- + gPanelsForWindow.set(win, new Set());
- + }
- + gPanelsForWindow.get(win).add(this._getPanelForNode(aPanel));
- + },
- +
- + removePanelCloseListeners(aPanel) {
- + Services.els.removeSystemEventListener(aPanel, "click", this, false);
- + Services.els.removeSystemEventListener(aPanel, "keypress", this, false);
- + const win = aPanel.ownerGlobal;
- + const panels = gPanelsForWindow.get(win);
- + if (panels) {
- + panels.delete(this._getPanelForNode(aPanel));
- + }
- + },
- +
- + handleEvent(aEvent) {
- + switch (aEvent.type) {
- + case "click":
- + case "keypress":
- + this.maybeAutoHidePanel(aEvent);
- + break;
- + }
- + },
- +
- + _getPanelForNode(aNode) {
- + return aNode.closest("panel");
- + },
- +
- + /*
- + * If people put things in the panel which need more than single-click interaction,
- + * we don't want to close it. Right now we check for text inputs and menu buttons.
- + * We also check for being outside of any toolbaritem/toolbarbutton, ie on a blank
- + * part of the menu.
- + */
- + _isOnInteractiveElement(aEvent) {
- + function getMenuPopupForDescendant(aNode) {
- + let lastPopup = null;
- + while (
- + aNode &&
- + aNode.parentNode &&
- + aNode.parentNode.localName.startsWith("menu")
- + ) {
- + lastPopup = aNode.localName == "menupopup" ? aNode : lastPopup;
- + aNode = aNode.parentNode;
- + }
- + return lastPopup;
- + }
- +
- + let target = aEvent.target;
- + const panel = this._getPanelForNode(aEvent.currentTarget);
- + // This can happen in e.g. customize mode. If there's no panel,
- + // there's clearly nothing for us to close; pretend we're interactive.
- + if (!panel) {
- + return true;
- + }
- + // We keep track of:
- + // whether we're in an input container (text field)
- + let inInput = false;
- + // whether we're in a popup/context menu
- + let inMenu = false;
- + // whether we're in a toolbarbutton/toolbaritem
- + let inItem = false;
- + // whether the current menuitem has a valid closemenu attribute
- + let menuitemCloseMenu = "auto";
- +
- + // While keeping track of that, we go from the original target back up,
- + // to the panel if we have to. We bail as soon as we find an input,
- + // a toolbarbutton/item, or the panel:
- + while (target) {
- + // Skip out of iframes etc:
- + if (target.nodeType == target.DOCUMENT_NODE) {
- + if (!target.defaultView) {
- + // Err, we're done.
- + break;
- + }
- + // Find containing browser or iframe element in the parent doc.
- + target = target.defaultView.docShell.chromeEventHandler;
- + if (!target) {
- + break;
- + }
- + }
- + const tagName = target.localName;
- + inInput = tagName == "input";
- + inItem = tagName == "toolbaritem" || tagName == "toolbarbutton";
- + const isMenuItem = tagName == "menuitem";
- + inMenu = inMenu || isMenuItem;
- +
- + if (isMenuItem && target.hasAttribute("closemenu")) {
- + const closemenuVal = target.getAttribute("closemenu");
- + menuitemCloseMenu =
- + closemenuVal == "single" || closemenuVal == "none"
- + ? closemenuVal
- + : "auto";
- + }
- +
- + // Keep the menu open and break out of the loop if the click happened on
- + // the ShadowRoot or a disabled menu item.
- + if (
- + target.nodeType == target.DOCUMENT_FRAGMENT_NODE ||
- + target.getAttribute("disabled") == "true"
- + ) {
- + return true;
- + }
- +
- + // This isn't in the loop condition because we want to break before
- + // changing |target| if any of these conditions are true
- + if (inInput || inItem || target == panel) {
- + break;
- + }
- + // We need specific code for popups: the item on which they were invoked
- + // isn't necessarily in their parentNode chain:
- + if (isMenuItem) {
- + const topmostMenuPopup = getMenuPopupForDescendant(target);
- + target =
- + (topmostMenuPopup && topmostMenuPopup.triggerNode) ||
- + target.parentNode;
- + } else {
- + target = target.parentNode;
- + }
- + }
- +
- + // If the user clicked a menu item...
- + if (inMenu) {
- + // We care if we're in an input also,
- + // or if the user specified closemenu!="auto":
- + if (inInput || menuitemCloseMenu != "auto") {
- + return true;
- + }
- + // Otherwise, we're probably fine to close the panel
- + return false;
- + }
- + // If we're not in a menu, and we *are* in a type="menu" toolbarbutton,
- + // we'll now interact with the menu
- + if (inItem && target.getAttribute("type") == "menu") {
- + return true;
- + }
- + return inInput || !inItem;
- + },
- +
- + hidePanelForNode(aNode) {
- + const panel = this._getPanelForNode(aNode);
- + if (panel) {
- + lazy.PanelMultiView.hidePopup(panel);
- + }
- + },
- +
- + maybeAutoHidePanel(aEvent) {
- + const eventType = aEvent.type;
- + if (eventType == "keypress" && aEvent.keyCode != aEvent.DOM_VK_RETURN) {
- + return;
- + }
- +
- + if (eventType == "click" && aEvent.button != 0) {
- + return;
- + }
- +
- + // We don't check preventDefault - it makes sense that this was prevented,
- + // but we probably still want to close the panel. If consumers don't want
- + // this to happen, they should specify the closemenu attribute.
- + if (eventType != "command" && this._isOnInteractiveElement(aEvent)) {
- + return;
- + }
- +
- + // We can't use event.target because we might have passed an anonymous
- + // content boundary as well, and so target points to the outer element in
- + // that case. Unfortunately, this means we get anonymous child nodes instead
- + // of the real ones, so looking for the 'stoooop, don't close me' attributes
- + // is more involved.
- + let target = aEvent.originalTarget;
- + while (target.parentNode && target.localName != "panel") {
- + if (
- + target.getAttribute("closemenu") == "none" ||
- + target.getAttribute("widget-type") == "view" ||
- + target.getAttribute("widget-type") == "button-and-view"
- + ) {
- + return;
- + }
- + target = target.parentNode;
- + }
- +
- + // If we get here, we can actually hide the popup:
- + this.hidePanelForNode(aEvent.target);
- + },
- +};
- +Object.freeze(CustomizableUIInternal);
- +
- +export var CustomizableUI = {
- + /**
- + * Constant reference to the ID of the navigation toolbar.
- + */
- + AREA_NAVBAR: "nav-bar",
- + /**
- + * Constant reference to the ID of the menubar's toolbar.
- + */
- + AREA_MENUBAR: "toolbar-menubar",
- + /**
- + * Constant reference to the ID of the tabstrip toolbar.
- + */
- + AREA_TABSTRIP: "TabsToolbar",
- + /**
- + * Constant reference to the ID of the bookmarks toolbar.
- + */
- + AREA_BOOKMARKS: "PersonalToolbar",
- + /**
- + * Constant reference to the ID of the non-dymanic (fixed) list in the overflow panel.
- + */
- + AREA_FIXED_OVERFLOW_PANEL: "widget-overflow-fixed-list",
- +
- + /**
- + * Constant indicating the area is a menu panel.
- + */
- + TYPE_MENU_PANEL: "menu-panel",
- + /**
- + * Constant indicating the area is a toolbar.
- + */
- + TYPE_TOOLBAR: "toolbar",
- +
- + /**
- + * Constant indicating a XUL-type provider.
- + */
- + PROVIDER_XUL: "xul",
- + /**
- + * Constant indicating an API-type provider.
- + */
- + PROVIDER_API: "api",
- + /**
- + * Constant indicating dynamic (special) widgets: spring, spacer, and separator.
- + */
- + PROVIDER_SPECIAL: "special",
- +
- + /**
- + * Constant indicating the widget is built-in
- + */
- + SOURCE_BUILTIN: "builtin",
- + /**
- + * Constant indicating the widget is externally provided
- + * (e.g. by add-ons or other items not part of the builtin widget set).
- + */
- + SOURCE_EXTERNAL: "external",
- +
- + /**
- + * Constant indicating the reason the event was fired was a window closing
- + */
- + REASON_WINDOW_CLOSED: "window-closed",
- + /**
- + * Constant indicating the reason the event was fired was an area being
- + * unregistered separately from window closing mechanics.
- + */
- + REASON_AREA_UNREGISTERED: "area-unregistered",
- +
- + /**
- + * Add a widget to an area.
- + * If the area to which you try to add is not known to CustomizableUI,
- + * this will throw.
- + * If the area to which you try to add is the same as the area in which
- + * the widget is currently placed, this will do the same as
- + * moveWidgetWithinArea.
- + * If the widget cannot be removed from its original location, this will
- + * no-op.
- + *
- + * This will fire an onWidgetAdded notification,
- + * and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification
- + * for each window CustomizableUI knows about.
- + *
- + * @param aWidgetId the ID of the widget to add
- + * @param aArea the ID of the area to add the widget to
- + * @param aPosition the position at which to add the widget. If you do not
- + * pass a position, the widget will be added to the end
- + * of the area.
- + */
- + addWidgetToArea(aWidgetId, aArea, aPosition) {},
- + /**
- + * Remove a widget from its area. If the widget cannot be removed from its
- + * area, or is not in any area, this will no-op. Otherwise, this will fire an
- + * onWidgetRemoved notification, and an onWidgetBeforeDOMChange and
- + * onWidgetAfterDOMChange notification for each window CustomizableUI knows
- + * about.
- + *
- + * @param aWidgetId the ID of the widget to remove
- + */
- + removeWidgetFromArea(aWidgetId) {},
- + /**
- + * Get the placement of a widget. This is by far the best way to obtain
- + * information about what the state of your widget is. The internals of
- + * this call are cheap (no DOM necessary) and you will know where the user
- + * has put your widget.
- + *
- + * @param aWidgetId the ID of the widget whose placement you want to know
- + * @returns
- + * {
- + * area: "somearea", // The ID of the area where the widget is placed
- + * position: 42 // the index in the placements array corresponding to
- + * // your widget.
- + * }
- + *
- + * OR
- + *
- + * null // if the widget is not placed anywhere (ie in the palette)
- + */
- + getPlacementOfWidget(aWidgetId, aOnlyRegistered = true, aDeadAreas = false) {
- + return null;
- + },
- + /**
- + * Add listeners to a panel that will close it. For use from the menu panel
- + * and overflowable toolbar implementations, unlikely to be useful for
- + * consumers.
- + *
- + * @param aPanel the panel to which listeners should be attached.
- + */
- + addPanelCloseListeners(aPanel) {
- + CustomizableUIInternal.addPanelCloseListeners(aPanel);
- + },
- + /**
- + * Remove close listeners that have been added to a panel with
- + * addPanelCloseListeners. For use from the menu panel and overflowable
- + * toolbar implementations, unlikely to be useful for consumers.
- + *
- + * @param aPanel the panel from which listeners should be removed.
- + */
- + removePanelCloseListeners(aPanel) {
- + CustomizableUIInternal.removePanelCloseListeners(aPanel);
- + },
- + /**
- + * Notify toolbox(es) of a particular event. If you don't pass aWindow,
- + * all toolboxes will be notified. For use from Customize Mode only,
- + * do not use otherwise.
- + *
- + * @param aEvent the name of the event to send.
- + * @param aDetails optional, the details of the event.
- + * @param aWindow optional, the window in which to send the event.
- + */
- + dispatchToolboxEvent(aEvent, aDetails = {}, aWindow = null) {},
- +};
- +Object.freeze(CustomizableUI);
- diff --git a/suite/buoy/modules/EzE10SUtils.sys.mjs b/suite/buoy/modules/EzE10SUtils.sys.mjs
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/modules/EzE10SUtils.sys.mjs
- @@ -0,0 +1,93 @@
- +/* 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 { E10SUtils } from "resource://gre/modules/E10SUtils.sys.mjs";
- +
- +import { ExtensionParent } from "resource://gre/modules/ExtensionParent.sys.mjs";
- +
- +export var EzE10SUtils = {
- + /**
- + * Loads about:blank in `browser` without switching remoteness. about:blank
- + * can load in a local browser or a remote browser, and `loadURI` will make
- + * it load in a remote browser even if you don't want it to.
- + *
- + * @param {nsIBrowser} browser
- + */
- + loadAboutBlank(browser) {
- + if (!browser.currentURI || browser.currentURI.spec == "about:blank") {
- + return;
- + }
- + browser.loadURI(Services.io.newURI("about:blank"), {
- + triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
- + remoteTypeOverride: browser.remoteType,
- + });
- + },
- +
- + /**
- + * Loads `uri` in `browser`, changing to a remote/local browser if necessary.
- + *
- + * @see `nsIWebNavigation.loadURI`
- + *
- + * @param {nsIBrowser} browser
- + * @param {string} uri
- + * @param {object} params
- + */
- + loadURI(browser, uri, params = {}) {
- + const multiProcess = browser.ownerGlobal.docShell.QueryInterface(
- + Ci.nsILoadContext
- + ).useRemoteTabs;
- + const remoteSubframes = browser.ownerGlobal.docShell.QueryInterface(
- + Ci.nsILoadContext
- + ).useRemoteSubframes;
- +
- + const isRemote = browser.getAttribute("remote") == "true";
- + const remoteType = E10SUtils.getRemoteTypeForURI(
- + uri,
- + multiProcess,
- + remoteSubframes
- + );
- + const shouldBeRemote = remoteType !== E10SUtils.NOT_REMOTE;
- +
- + if (shouldBeRemote != isRemote) {
- + this.changeRemoteness(browser, remoteType);
- + }
- +
- + params.triggeringPrincipal =
- + params.triggeringPrincipal ||
- + Services.scriptSecurityManager.getSystemPrincipal();
- + browser.fixupAndLoadURIString(uri, params);
- + },
- +
- + /**
- + * Force `browser` to be a remote/local browser.
- + *
- + * @see E10SUtils.sys.mjs for remote types.
- + *
- + * @param {nsIBrowser} browser - the browser to enforce the remoteness of.
- + * @param {string} remoteType - the remoteness to enforce.
- + * @returns {boolean} true if any change happened on the browser (which would
- + * not be the case if its remoteness is already in the correct state).
- + */
- + changeRemoteness(browser, remoteType) {
- + if (browser.remoteType == remoteType) {
- + return false;
- + }
- +
- + browser.destroy();
- +
- + if (remoteType) {
- + browser.setAttribute("remote", "true");
- + browser.setAttribute("remoteType", remoteType);
- + } else {
- + browser.setAttribute("remote", "false");
- + browser.removeAttribute("remoteType");
- + }
- +
- + browser.changeRemoteness({ remoteType });
- + browser.construct();
- + ExtensionParent.apiManager.emit("extension-browser-inserted", browser);
- +
- + return true;
- + },
- +};
- diff --git a/suite/buoy/modules/PanelMultiView.sys.mjs b/suite/buoy/modules/PanelMultiView.sys.mjs
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/modules/PanelMultiView.sys.mjs
- @@ -0,0 +1,1700 @@
- +/* 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/. */
- +
- +/**
- + * Allows a popup panel to host multiple subviews. The main view shown when the
- + * panel is opened may slide out to display a subview, which in turn may lead to
- + * other subviews in a cascade menu pattern.
- + *
- + * The <panel> element should contain a <panelmultiview> element. Views are
- + * declared using <panelview> elements that are usually children of the main
- + * <panelmultiview> element, although they don't need to be, as views can also
- + * be imported into the panel from other panels or popup sets.
- + *
- + * The panel should be opened asynchronously using the openPopup static method
- + * on the PanelMultiView object. This will display the view specified using the
- + * mainViewId attribute on the contained <panelmultiview> element.
- + *
- + * Specific subviews can slide in using the showSubView method, and backwards
- + * navigation can be done using the goBack method or through a button in the
- + * subview headers.
- + *
- + * The process of displaying the main view or a new subview requires multiple
- + * steps to be completed, hence at any given time the <panelview> element may
- + * be in different states:
- + *
- + * -- Open or closed
- + *
- + * All the <panelview> elements start "closed", meaning that they are not
- + * associated to a <panelmultiview> element and can be located anywhere in
- + * the document. When the openPopup or showSubView methods are called, the
- + * relevant view becomes "open" and the <panelview> element may be moved to
- + * ensure it is a descendant of the <panelmultiview> element.
- + *
- + * The "ViewShowing" event is fired at this point, when the view is not
- + * visible yet. The event is allowed to cancel the operation, in which case
- + * the view is closed immediately.
- + *
- + * Closing the view does not move the node back to its original position.
- + *
- + * -- Visible or invisible
- + *
- + * This indicates whether the view is visible in the document from a layout
- + * perspective, regardless of whether it is currently scrolled into view. In
- + * fact, all subviews are already visible before they start sliding in.
- + *
- + * Before scrolling into view, a view may become visible but be placed in a
- + * special off-screen area of the document where layout and measurements can
- + * take place asynchronously.
- + *
- + * When navigating forward, an open view may become invisible but stay open
- + * after sliding out of view. The last known size of these views is still
- + * taken into account for determining the overall panel size.
- + *
- + * When navigating backwards, an open subview will first become invisible and
- + * then will be closed.
- + *
- + * -- Active or inactive
- + *
- + * This indicates whether the view is fully scrolled into the visible area
- + * and ready to receive mouse and keyboard events. An active view is always
- + * visible, but a visible view may be inactive. For example, during a scroll
- + * transition, both views will be inactive.
- + *
- + * When a view becomes active, the ViewShown event is fired synchronously,
- + * and the showSubView and goBack methods can be called for navigation.
- + *
- + * For the main view of the panel, the ViewShown event is dispatched during
- + * the "popupshown" event, which means that other "popupshown" handlers may
- + * be called before the view is active. Thus, code that needs to perform
- + * further navigation automatically should either use the ViewShown event or
- + * wait for an event loop tick, like BrowserTestUtils.waitForEvent does.
- + *
- + * -- Navigating with the keyboard
- + *
- + * An open view may keep state related to keyboard navigation, even if it is
- + * invisible. When a view is closed, keyboard navigation state is cleared.
- + *
- + * This diagram shows how <panelview> nodes move during navigation:
- + *
- + * In this <panelmultiview> In other panels Action
- + * ┌───┬───┬───┐ ┌───┬───┐
- + * │(A)│ B │ C │ │ D │ E │ Open panel
- + * └───┴───┴───┘ └───┴───┘
- + * ┌───┬───┬───┐ ┌───┬───┐
- + * │{A}│(C)│ B │ │ D │ E │ Show subview C
- + * └───┴───┴───┘ └───┴───┘
- + * ┌───┬───┬───┬───┐ ┌───┐
- + * │{A}│{C}│(D)│ B │ │ E │ Show subview D
- + * └───┴───┴───┴───┘ └───┘
- + * │ ┌───┬───┬───┬───┐ ┌───┐
- + * │ │{A}│(C)│ D │ B │ │ E │ Go back
- + * │ └───┴───┴───┴───┘ └───┘
- + * │ │ │
- + * │ │ └── Currently visible view
- + * │ │ │
- + * └───┴───┴── Open views
- + */
- +
- +const lazy = {};
- +
- +ChromeUtils.defineLazyGetter(lazy, "gBundle", function () {
- + return Services.strings.createBundle(
- + "chrome://messenger/locale/messenger.properties"
- + );
- +});
- +
- +/**
- + * Safety timeout after which asynchronous events will be canceled if any of the
- + * registered blockers does not return.
- + */
- +const BLOCKERS_TIMEOUT_MS = 10000;
- +
- +const TRANSITION_PHASES = Object.freeze({
- + START: 1,
- + PREPARE: 2,
- + TRANSITION: 3,
- +});
- +
- +const gNodeToObjectMap = new WeakMap();
- +const gWindowsWithUnloadHandler = new WeakSet();
- +
- +/**
- + * Allows associating an object to a node lazily using a weak map.
- + *
- + * Classes deriving from this one may be easily converted to Custom Elements,
- + * although they would lose the ability of being associated lazily.
- + */
- +var AssociatedToNode = class {
- + constructor(node) {
- + /**
- + * Node associated to this object.
- + */
- + this.node = node;
- +
- + /**
- + * This promise is resolved when the current set of blockers set by event
- + * handlers have all been processed.
- + */
- + this._blockersPromise = Promise.resolve();
- + }
- +
- + /**
- + * Retrieves the instance associated with the given node, constructing a new
- + * one if necessary. When the last reference to the node is released, the
- + * object instance will be garbage collected as well.
- + */
- + static forNode(node) {
- + let associatedToNode = gNodeToObjectMap.get(node);
- + if (!associatedToNode) {
- + associatedToNode = new this(node);
- + gNodeToObjectMap.set(node, associatedToNode);
- + }
- + return associatedToNode;
- + }
- +
- + get document() {
- + return this.node.ownerDocument;
- + }
- +
- + get window() {
- + return this.node.ownerGlobal;
- + }
- +
- + _getBoundsWithoutFlushing(element) {
- + return this.window.windowUtils.getBoundsWithoutFlushing(element);
- + }
- +
- + /**
- + * Dispatches a custom event on this element.
- + *
- + * @param {string} eventName Name of the event to dispatch.
- + * @param {object} [detail] Event detail object. Optional.
- + * @param {boolean} cancelable If the event can be canceled.
- + * @returns {boolean} `true` if the event was canceled by an event handler, `false`
- + * otherwise.
- + */
- + dispatchCustomEvent(eventName, detail, cancelable = false) {
- + const event = new this.window.CustomEvent(eventName, {
- + detail,
- + bubbles: true,
- + cancelable,
- + });
- + this.node.dispatchEvent(event);
- + return event.defaultPrevented;
- + }
- +
- + /**
- + * Dispatches a custom event on this element and waits for any blocking
- + * promises registered using the "addBlocker" function on the details object.
- + * If this function is called again, the event is only dispatched after all
- + * the previously registered blockers have returned.
- + *
- + * The event can be canceled either by resolving any blocking promise to the
- + * boolean value "false" or by calling preventDefault on the event. Rejections
- + * and exceptions will be reported and will cancel the event.
- + *
- + * Blocking should be used sporadically because it slows down the interface.
- + * Also, non-reentrancy is not strictly guaranteed because a safety timeout of
- + * BLOCKERS_TIMEOUT_MS is implemented, after which the event will be canceled.
- + * This helps to prevent deadlocks if any of the event handlers does not
- + * resolve a blocker promise.
- + *
- + * @note Since there is no use case for dispatching different asynchronous
- + * events in parallel for the same element, this function will also wait
- + * for previous blockers when the event name is different.
- + *
- + * @param eventName
- + * Name of the custom event to dispatch.
- + *
- + * @resolves True if the event was canceled by a handler, false otherwise.
- + */
- + async dispatchAsyncEvent(eventName) {
- + // Wait for all the previous blockers before dispatching the event.
- + const blockersPromise = this._blockersPromise.catch(() => {});
- + return (this._blockersPromise = blockersPromise.then(async () => {
- + const blockers = new Set();
- + let cancel = this.dispatchCustomEvent(
- + eventName,
- + {
- + addBlocker(promise) {
- + // Any exception in the blocker will cancel the operation.
- + blockers.add(
- + promise.catch(ex => {
- + console.error(ex);
- + return true;
- + })
- + );
- + },
- + },
- + true
- + );
- + if (blockers.size) {
- + const timeoutPromise = new Promise((resolve, reject) => {
- + this.window.setTimeout(reject, BLOCKERS_TIMEOUT_MS);
- + });
- + try {
- + const results = await Promise.race([
- + Promise.all(blockers),
- + timeoutPromise,
- + ]);
- + cancel = cancel || results.some(result => result === false);
- + } catch (ex) {
- + console.error(
- + new Error(`One of the blockers for ${eventName} timed out.`)
- + );
- + return true;
- + }
- + }
- + return cancel;
- + }));
- + }
- +};
- +
- +/**
- + * This is associated to <panelmultiview> elements.
- + */
- +export class PanelMultiView extends AssociatedToNode {
- + /**
- + * Tries to open the specified <panel> and displays the main view specified
- + * with the "mainViewId" attribute on the <panelmultiview> node it contains.
- + *
- + * If the panel does not contain a <panelmultiview>, it is opened directly.
- + * This allows consumers like page actions to accept different panel types.
- + *
- + * @see The non-static openPopup method for details.
- + */
- + static async openPopup(panelNode, ...args) {
- + const panelMultiViewNode = panelNode.querySelector("panelmultiview");
- + if (panelMultiViewNode) {
- + return this.forNode(panelMultiViewNode).openPopup(...args);
- + }
- + panelNode.openPopup(...args);
- + return true;
- + }
- +
- + /**
- + * Closes the specified <panel> which contains a <panelmultiview> node.
- + *
- + * If the panel does not contain a <panelmultiview>, it is closed directly.
- + * This allows consumers like page actions to accept different panel types.
- + *
- + * @see The non-static hidePopup method for details.
- + */
- + static hidePopup(panelNode) {
- + const panelMultiViewNode = panelNode.querySelector("panelmultiview");
- + if (panelMultiViewNode) {
- + this.forNode(panelMultiViewNode).hidePopup();
- + } else {
- + panelNode.hidePopup();
- + }
- + }
- +
- + /**
- + * Removes the specified <panel> from the document, ensuring that any
- + * <panelmultiview> node it contains is destroyed properly.
- + *
- + * If the viewCacheId attribute is present on the <panelmultiview> element,
- + * imported subviews will be moved out again to the element it specifies, so
- + * that the panel element can be removed safely.
- + *
- + * If the panel does not contain a <panelmultiview>, it is removed directly.
- + * This allows consumers like page actions to accept different panel types.
- + */
- + static removePopup(panelNode) {
- + try {
- + const panelMultiViewNode = panelNode.querySelector("panelmultiview");
- + if (panelMultiViewNode) {
- + const panelMultiView = this.forNode(panelMultiViewNode);
- + panelMultiView._moveOutKids();
- + panelMultiView.disconnect();
- + }
- + } finally {
- + // Make sure to remove the panel element even if disconnecting fails.
- + panelNode.remove();
- + }
- + }
- +
- + /**
- + * Ensures that when the specified window is closed all the <panelmultiview>
- + * node it contains are destroyed properly.
- + */
- + static ensureUnloadHandlerRegistered(window) {
- + if (gWindowsWithUnloadHandler.has(window)) {
- + return;
- + }
- +
- + window.addEventListener(
- + "unload",
- + () => {
- + for (const panelMultiViewNode of window.document.querySelectorAll(
- + "panelmultiview"
- + )) {
- + this.forNode(panelMultiViewNode).disconnect();
- + }
- + },
- + { once: true }
- + );
- +
- + gWindowsWithUnloadHandler.add(window);
- + }
- +
- + get _panel() {
- + return this.node.parentNode;
- + }
- +
- + set _transitioning(val) {
- + if (val) {
- + this.node.setAttribute("transitioning", "true");
- + } else {
- + this.node.removeAttribute("transitioning");
- + }
- + }
- +
- + get _screenManager() {
- + if (this.__screenManager) {
- + return this.__screenManager;
- + }
- + return (this.__screenManager = Cc[
- + "@mozilla.org/gfx/screenmanager;1"
- + ].getService(Ci.nsIScreenManager));
- + }
- +
- + constructor(node) {
- + super(node);
- + this._openPopupPromise = Promise.resolve(false);
- + this._openPopupCancelCallback = () => {};
- + }
- +
- + connect() {
- + this.connected = true;
- +
- + PanelMultiView.ensureUnloadHandlerRegistered(this.window);
- +
- + const viewContainer = (this._viewContainer =
- + this.document.createXULElement("box"));
- + viewContainer.classList.add("panel-viewcontainer");
- +
- + const viewStack = (this._viewStack = this.document.createXULElement("box"));
- + viewStack.classList.add("panel-viewstack");
- + viewContainer.append(viewStack);
- +
- + const offscreenViewContainer = this.document.createXULElement("box");
- + offscreenViewContainer.classList.add("panel-viewcontainer", "offscreen");
- +
- + const offscreenViewStack = (this._offscreenViewStack =
- + this.document.createXULElement("box"));
- + offscreenViewStack.classList.add("panel-viewstack");
- + offscreenViewContainer.append(offscreenViewStack);
- +
- + this.node.prepend(offscreenViewContainer);
- + this.node.prepend(viewContainer);
- +
- + this.openViews = [];
- +
- + this._panel.addEventListener("popupshowing", this);
- + this._panel.addEventListener("popuppositioned", this);
- + this._panel.addEventListener("popuphidden", this);
- + this._panel.addEventListener("popupshown", this);
- +
- + // Proxy these public properties and methods, as used elsewhere by various
- + // parts of the browser, to this instance.
- + ["goBack", "showSubView"].forEach(method => {
- + Object.defineProperty(this.node, method, {
- + enumerable: true,
- + value: (...args) => this[method](...args),
- + });
- + });
- + }
- +
- + disconnect() {
- + // Guard against re-entrancy.
- + if (!this.node || !this.connected) {
- + return;
- + }
- +
- + this._panel.removeEventListener("mousemove", this);
- + this._panel.removeEventListener("popupshowing", this);
- + this._panel.removeEventListener("popuppositioned", this);
- + this._panel.removeEventListener("popupshown", this);
- + this._panel.removeEventListener("popuphidden", this);
- + this.window.removeEventListener("keydown", this, true);
- + this.node =
- + this._openPopupPromise =
- + this._openPopupCancelCallback =
- + this._viewContainer =
- + this._viewStack =
- + this._transitionDetails =
- + null;
- + }
- +
- + /**
- + * Tries to open the panel associated with this PanelMultiView, and displays
- + * the main view specified with the "mainViewId" attribute.
- + *
- + * The hidePopup method can be called while the operation is in progress to
- + * prevent the panel from being displayed. View events may also cancel the
- + * operation, so there is no guarantee that the panel will become visible.
- + *
- + * The "popuphidden" event will be fired either when the operation is canceled
- + * or when the popup is closed later. This event can be used for example to
- + * reset the "open" state of the anchor or tear down temporary panels.
- + *
- + * If this method is called again before the panel is shown, the result
- + * depends on the operation currently in progress. If the operation was not
- + * canceled, the panel is opened using the arguments from the previous call,
- + * and this call is ignored. If the operation was canceled, it will be
- + * retried again using the arguments from this call.
- + *
- + * It's not necessary for the <panelmultiview> binding to be connected when
- + * this method is called, but the containing panel must have its display
- + * turned on, for example it shouldn't have the "hidden" attribute.
- + *
- + * @param anchor
- + * The node to anchor the popup to.
- + * @param options
- + * Either options to use or a string position. This is forwarded to
- + * the openPopup method of the panel.
- + * @param args
- + * Additional arguments to be forwarded to the openPopup method of the
- + * panel.
- + *
- + * @resolves With true as soon as the request to display the panel has been
- + * sent, or with false if the operation was canceled. The state of
- + * the panel at this point is not guaranteed. It may be still
- + * showing, completely shown, or completely hidden.
- + * @rejects If an exception is thrown at any point in the process before the
- + * request to display the panel is sent.
- + */
- + async openPopup(anchor, options, ...args) {
- + // Set up the function that allows hidePopup or a second call to showPopup
- + // to cancel the specific panel opening operation that we're starting below.
- + // This function must be synchronous, meaning we can't use Promise.race,
- + // because hidePopup wants to dispatch the "popuphidden" event synchronously
- + // even if the panel has not been opened yet.
- + let canCancel = true;
- + const cancelCallback = (this._openPopupCancelCallback = () => {
- + // If the cancel callback is called and the panel hasn't been prepared
- + // yet, cancel showing it. Setting canCancel to false will prevent the
- + // popup from opening. If the panel has opened by the time the cancel
- + // callback is called, canCancel will be false already, and we will not
- + // fire the "popuphidden" event.
- + if (canCancel && this.node) {
- + canCancel = false;
- + this.dispatchCustomEvent("popuphidden");
- + }
- + });
- +
- + // Create a promise that is resolved with the result of the last call to
- + // this method, where errors indicate that the panel was not opened.
- + const openPopupPromise = this._openPopupPromise.catch(() => {
- + return false;
- + });
- +
- + // Make the preparation done before showing the panel non-reentrant. The
- + // promise created here will be resolved only after the panel preparation is
- + // completed, even if a cancellation request is received in the meantime.
- + return (this._openPopupPromise = openPopupPromise.then(async wasShown => {
- + // The panel may have been destroyed in the meantime.
- + if (!this.node) {
- + return false;
- + }
- + // If the panel has been already opened there is nothing more to do. We
- + // check the actual state of the panel rather than setting some state in
- + // our handler of the "popuphidden" event because this has a lower chance
- + // of locking indefinitely if events aren't raised in the expected order.
- + if (wasShown && ["open", "showing"].includes(this._panel.state)) {
- + return true;
- + }
- + try {
- + if (!this.connected) {
- + this.connect();
- + }
- + // Allow any of the ViewShowing handlers to prevent showing the main view.
- + if (!(await this._showMainView())) {
- + cancelCallback();
- + }
- + } catch (ex) {
- + cancelCallback();
- + throw ex;
- + }
- + // If a cancellation request was received there is nothing more to do.
- + if (!canCancel || !this.node) {
- + return false;
- + }
- + // We have to set canCancel to false before opening the popup because the
- + // hidePopup method of PanelMultiView can be re-entered by event handlers.
- + // If the openPopup call fails, however, we still have to dispatch the
- + // "popuphidden" event even if canCancel was set to false.
- + try {
- + canCancel = false;
- + this._panel.openPopup(anchor, options, ...args);
- +
- + // On Windows, if another popup is hiding while we call openPopup, the
- + // call won't fail but the popup won't open. In this case, we have to
- + // dispatch an artificial "popuphidden" event to reset our state.
- + if (this._panel.state == "closed" && this.openViews.length) {
- + this.dispatchCustomEvent("popuphidden");
- + return false;
- + }
- +
- + if (
- + options &&
- + typeof options == "object" &&
- + options.triggerEvent &&
- + options.triggerEvent.type == "keypress" &&
- + this.openViews.length
- + ) {
- + // This was opened via the keyboard, so focus the first item.
- + this.openViews[0].focusWhenActive = true;
- + }
- +
- + return true;
- + } catch (ex) {
- + this.dispatchCustomEvent("popuphidden");
- + throw ex;
- + }
- + }));
- + }
- +
- + /**
- + * Closes the panel associated with this PanelMultiView.
- + *
- + * If the openPopup method was called but the panel has not been displayed
- + * yet, the operation is canceled and the panel will not be displayed, but the
- + * "popuphidden" event is fired synchronously anyways.
- + *
- + * This means that by the time this method returns all the operations handled
- + * by the "popuphidden" event are completed, for example resetting the "open"
- + * state of the anchor, and the panel is already invisible.
- + */
- + hidePopup() {
- + if (!this.node || !this.connected) {
- + return;
- + }
- +
- + // If we have already reached the _panel.openPopup call in the openPopup
- + // method, we can call hidePopup. Otherwise, we have to cancel the latest
- + // request to open the panel, which will have no effect if the request has
- + // been canceled already.
- + if (["open", "showing"].includes(this._panel.state)) {
- + this._panel.hidePopup();
- + } else {
- + this._openPopupCancelCallback();
- + }
- +
- + // We close all the views synchronously, so that they are ready to be opened
- + // in other PanelMultiView instances. The "popuphidden" handler may also
- + // call this function, but the second time openViews will be empty.
- + this.closeAllViews();
- + }
- +
- + /**
- + * Move any child subviews into the element defined by "viewCacheId" to make
- + * sure they will not be removed together with the <panelmultiview> element.
- + */
- + _moveOutKids() {
- + const viewCacheId = this.node.getAttribute("viewCacheId");
- + if (!viewCacheId) {
- + return;
- + }
- +
- + // Node.children and Node.children is live to DOM changes like the
- + // ones we're about to do, so iterate over a static copy:
- + const subviews = Array.from(this._viewStack.children);
- + const viewCache = this.document.getElementById(viewCacheId);
- + for (const subview of subviews) {
- + viewCache.appendChild(subview);
- + }
- + }
- +
- + /**
- + * Slides in the specified view as a subview.
- + *
- + * @param viewIdOrNode
- + * DOM element or string ID of the <panelview> to display.
- + * @param anchor
- + * DOM element that triggered the subview, which will be highlighted
- + * and whose "label" attribute will be used for the title of the
- + * subview when a "title" attribute is not specified.
- + */
- + showSubView(viewIdOrNode, anchor) {
- + this._showSubView(viewIdOrNode, anchor).catch(console.error);
- + }
- + async _showSubView(viewIdOrNode, anchor) {
- + const viewNode =
- + typeof viewIdOrNode == "string"
- + ? this.document.getElementById(viewIdOrNode)
- + : viewIdOrNode;
- + if (!viewNode) {
- + console.error(new Error(`Subview ${viewIdOrNode} doesn't exist.`));
- + return;
- + }
- +
- + if (!this.openViews.length) {
- + console.error(new Error(`Cannot show a subview in a closed panel.`));
- + return;
- + }
- +
- + const prevPanelView = this.openViews[this.openViews.length - 1];
- + const nextPanelView = PanelView.forNode(viewNode);
- + if (this.openViews.includes(nextPanelView)) {
- + console.error(new Error(`Subview ${viewNode.id} is already open.`));
- + return;
- + }
- +
- + // Do not re-enter the process if navigation is already in progress. Since
- + // there is only one active view at any given time, we can do this check
- + // safely, even considering that during the navigation process the actual
- + // view to which prevPanelView refers will change.
- + if (!prevPanelView.active) {
- + return;
- + }
- + // If prevPanelView._doingKeyboardActivation is true, it will be reset to
- + // false synchronously. Therefore, we must capture it before we use any
- + // "await" statements.
- + const doingKeyboardActivation = prevPanelView._doingKeyboardActivation;
- + // Marking the view that is about to scrolled out of the visible area as
- + // inactive will prevent re-entrancy and also disable keyboard navigation.
- + // From this point onwards, "await" statements can be used safely.
- + prevPanelView.active = false;
- +
- + // Provide visual feedback while navigation is in progress, starting before
- + // the transition starts and ending when the previous view is invisible.
- + if (anchor) {
- + anchor.setAttribute("open", "true");
- + }
- + try {
- + // If the ViewShowing event cancels the operation we have to re-enable
- + // keyboard navigation, but this must be avoided if the panel was closed.
- + if (!(await this._openView(nextPanelView))) {
- + if (prevPanelView.isOpenIn(this)) {
- + // We don't raise a ViewShown event because nothing actually changed.
- + // Technically we should use a different state flag just because there
- + // is code that could check the "active" property to determine whether
- + // to wait for a ViewShown event later, but this only happens in
- + // regression tests and is less likely to be a technique used in
- + // production code, where use of ViewShown is less common.
- + prevPanelView.active = true;
- + }
- + return;
- + }
- +
- + prevPanelView.captureKnownSize();
- +
- + // The main view of a panel can be a subview in another one. Make sure to
- + // reset all the properties that may be set on a subview.
- + nextPanelView.mainview = false;
- + // The header may change based on how the subview was opened.
- + nextPanelView.headerText =
- + viewNode.getAttribute("title") ||
- + (anchor && anchor.getAttribute("label"));
- + // The constrained width of subviews may also vary between panels.
- + nextPanelView.minMaxWidth = prevPanelView.knownWidth;
- +
- + if (anchor) {
- + viewNode.classList.add("PanelUI-subView");
- + }
- +
- + await this._transitionViews(prevPanelView.node, viewNode, false, anchor);
- + } finally {
- + if (anchor) {
- + anchor.removeAttribute("open");
- + }
- + }
- +
- + nextPanelView.focusWhenActive = doingKeyboardActivation;
- + this._activateView(nextPanelView);
- + }
- +
- + /**
- + * Navigates backwards by sliding out the most recent subview.
- + */
- + goBack() {
- + this._goBack().catch(console.error);
- + }
- + async _goBack() {
- + if (this.openViews.length < 2) {
- + // This may be called by keyboard navigation or external code when only
- + // the main view is open.
- + return;
- + }
- +
- + const prevPanelView = this.openViews[this.openViews.length - 1];
- + const nextPanelView = this.openViews[this.openViews.length - 2];
- +
- + // Like in the showSubView method, do not re-enter navigation while it is
- + // in progress, and make the view inactive immediately. From this point
- + // onwards, "await" statements can be used safely.
- + if (!prevPanelView.active) {
- + return;
- + }
- +
- + prevPanelView.active = false;
- +
- + prevPanelView.captureKnownSize();
- +
- + await this._transitionViews(prevPanelView.node, nextPanelView.node, true);
- +
- + this._closeLatestView();
- +
- + this._activateView(nextPanelView);
- + }
- +
- + /**
- + * Prepares the main view before showing the panel.
- + */
- + async _showMainView() {
- + const nextPanelView = PanelView.forNode(
- + this.document.getElementById(this.node.getAttribute("mainViewId"))
- + );
- +
- + // If the view is already open in another panel, close the panel first.
- + const oldPanelMultiViewNode = nextPanelView.node.panelMultiView;
- + if (oldPanelMultiViewNode) {
- + PanelMultiView.forNode(oldPanelMultiViewNode).hidePopup();
- + // Wait for a layout flush after hiding the popup, otherwise the view may
- + // not be displayed correctly for some time after the new panel is opened.
- + // This is filed as bug 1441015.
- + await this.window.promiseDocumentFlushed(() => {});
- + }
- +
- + if (!(await this._openView(nextPanelView))) {
- + return false;
- + }
- +
- + // The main view of a panel can be a subview in another one. Make sure to
- + // reset all the properties that may be set on a subview.
- + nextPanelView.mainview = true;
- + nextPanelView.headerText = "";
- + nextPanelView.minMaxWidth = 0;
- +
- + // Ensure the view will be visible once the panel is opened.
- + nextPanelView.visible = true;
- +
- + return true;
- + }
- +
- + /**
- + * Opens the specified PanelView and dispatches the ViewShowing event, which
- + * can be used to populate the subview or cancel the operation.
- + *
- + * This also clears all the attributes and styles that may be left by a
- + * transition that was interrupted.
- + *
- + * @resolves With true if the view was opened, false otherwise.
- + */
- + async _openView(panelView) {
- + if (panelView.node.parentNode != this._viewStack) {
- + this._viewStack.appendChild(panelView.node);
- + }
- +
- + panelView.node.panelMultiView = this.node;
- + this.openViews.push(panelView);
- +
- + const canceled = await panelView.dispatchAsyncEvent("ViewShowing");
- +
- + // The panel can be hidden while we are processing the ViewShowing event.
- + // This results in all the views being closed synchronously, and at this
- + // point the ViewHiding event has already been dispatched for all of them.
- + if (!this.openViews.length) {
- + return false;
- + }
- +
- + // Check if the event requested cancellation but the panel is still open.
- + if (canceled) {
- + // Handlers for ViewShowing can't know if a different handler requested
- + // cancellation, so this will dispatch a ViewHiding event to give a chance
- + // to clean up.
- + this._closeLatestView();
- + return false;
- + }
- +
- + // Clean up all the attributes and styles related to transitions. We do this
- + // here rather than when the view is closed because we are likely to make
- + // other DOM modifications soon, which isn't the case when closing.
- + const { style } = panelView.node;
- + style.removeProperty("outline");
- + style.removeProperty("width");
- +
- + return true;
- + }
- +
- + /**
- + * Activates the specified view and raises the ViewShown event, unless the
- + * view was closed in the meantime.
- + */
- + _activateView(panelView) {
- + if (panelView.isOpenIn(this)) {
- + panelView.active = true;
- + if (panelView.focusWhenActive) {
- + panelView.focusFirstNavigableElement(false, true);
- + panelView.focusWhenActive = false;
- + }
- + panelView.dispatchCustomEvent("ViewShown");
- + }
- + }
- +
- + /**
- + * Closes the most recent PanelView and raises the ViewHiding event.
- + *
- + * @note The ViewHiding event is not cancelable and should probably be renamed
- + * to ViewHidden or ViewClosed instead, see bug 1438507.
- + */
- + _closeLatestView() {
- + const panelView = this.openViews.pop();
- + panelView.clearNavigation();
- + panelView.dispatchCustomEvent("ViewHiding");
- + panelView.node.panelMultiView = null;
- + // Views become invisible synchronously when they are closed, and they won't
- + // become visible again until they are opened. When this is called at the
- + // end of backwards navigation, the view is already invisible.
- + panelView.visible = false;
- + }
- +
- + /**
- + * Closes all the views that are currently open.
- + */
- + closeAllViews() {
- + // Raise ViewHiding events for open views in reverse order.
- + while (this.openViews.length) {
- + this._closeLatestView();
- + }
- + }
- +
- + /**
- + * Apply a transition to 'slide' from the currently active view to the next
- + * one.
- + * Sliding the next subview in means that the previous panelview stays where it
- + * is and the active panelview slides in from the left in LTR mode, right in
- + * RTL mode.
- + *
- + * @param {panelview} previousViewNode Node that is currently displayed, but
- + * is about to be transitioned away. This
- + * must be already inactive at this point.
- + * @param {panelview} viewNode - Node that will becode the active view,
- + * after the transition has finished.
- + * @param {boolean} reverse Whether we're navigation back to a
- + * previous view or forward to a next view.
- + */
- + async _transitionViews(previousViewNode, viewNode, reverse) {
- + const { window } = this;
- +
- + const nextPanelView = PanelView.forNode(viewNode);
- + const prevPanelView = PanelView.forNode(previousViewNode);
- +
- + const details = (this._transitionDetails = {
- + phase: TRANSITION_PHASES.START,
- + });
- +
- + // Set the viewContainer dimensions to make sure only the current view is
- + // visible.
- + const olderView = reverse ? nextPanelView : prevPanelView;
- + this._viewContainer.style.minHeight = olderView.knownHeight + "px";
- + this._viewContainer.style.height = prevPanelView.knownHeight + "px";
- + this._viewContainer.style.width = prevPanelView.knownWidth + "px";
- + // Lock the dimensions of the window that hosts the popup panel.
- + const rect = this._getBoundsWithoutFlushing(this._panel);
- + this._panel.style.width = rect.width + "px";
- + this._panel.style.height = rect.height + "px";
- +
- + let viewRect;
- + if (reverse) {
- + // Use the cached size when going back to a previous view, but not when
- + // reopening a subview, because its contents may have changed.
- + viewRect = {
- + width: nextPanelView.knownWidth,
- + height: nextPanelView.knownHeight,
- + };
- + nextPanelView.visible = true;
- + } else if (viewNode.customRectGetter) {
- + // We use a customRectGetter for WebExtensions panels, because they need
- + // to query the size from an embedded browser. The presence of this
- + // getter also provides an indication that the view node shouldn't be
- + // moved around, otherwise the state of the browser would get disrupted.
- + const width = prevPanelView.knownWidth;
- + const height = prevPanelView.knownHeight;
- + viewRect = Object.assign({ height, width }, viewNode.customRectGetter());
- + nextPanelView.visible = true;
- + // Until the header is visible, it has 0 height.
- + // Wait for layout before measuring it
- + const header = viewNode.firstElementChild;
- + if (header && header.classList.contains("panel-header")) {
- + viewRect.height += await window.promiseDocumentFlushed(() => {
- + return this._getBoundsWithoutFlushing(header).height;
- + });
- + }
- + } else {
- + this._offscreenViewStack.style.minHeight = olderView.knownHeight + "px";
- + this._offscreenViewStack.appendChild(viewNode);
- + nextPanelView.visible = true;
- +
- + viewRect = await window.promiseDocumentFlushed(() => {
- + return this._getBoundsWithoutFlushing(viewNode);
- + });
- + // Bail out if the panel was closed in the meantime.
- + if (!nextPanelView.isOpenIn(this)) {
- + return;
- + }
- +
- + // Place back the view after all the other views that are already open in
- + // order for the transition to work as expected.
- + this._viewStack.appendChild(viewNode);
- +
- + this._offscreenViewStack.style.removeProperty("min-height");
- + }
- +
- + this._transitioning = true;
- + details.phase = TRANSITION_PHASES.PREPARE;
- +
- + // The 'magic' part: build up the amount of pixels to move right or left.
- + const moveToLeft =
- + (this.window.RTL_UI && !reverse) || (!this.window.RTL_UI && reverse);
- + const deltaX = prevPanelView.knownWidth;
- + const deepestNode = reverse ? previousViewNode : viewNode;
- +
- + // With a transition when navigating backwards - user hits the 'back'
- + // button - we need to make sure that the views are positioned in a way
- + // that a translateX() unveils the previous view from the right direction.
- + if (reverse) {
- + this._viewStack.style.marginInlineStart = "-" + deltaX + "px";
- + }
- +
- + // Set the transition style and listen for its end to clean up and make sure
- + // the box sizing becomes dynamic again.
- + // Somehow, putting these properties in PanelUI.css doesn't work for newly
- + // shown nodes in a XUL parent node.
- + this._viewStack.style.transition =
- + "transform var(--animation-easing-function)" +
- + " var(--panelui-subview-transition-duration)";
- + this._viewStack.style.willChange = "transform";
- + // Use an outline instead of a border so that the size is not affected.
- + deepestNode.style.outline = "1px solid var(--panel-separator-color)";
- +
- + // Now that all the elements are in place for the start of the transition,
- + // give the layout code a chance to set the initial values.
- + await window.promiseDocumentFlushed(() => {});
- + // Bail out if the panel was closed in the meantime.
- + if (!nextPanelView.isOpenIn(this)) {
- + return;
- + }
- +
- + // Now set the viewContainer dimensions to that of the new view, which
- + // kicks of the height animation.
- + this._viewContainer.style.height = viewRect.height + "px";
- + this._viewContainer.style.width = viewRect.width + "px";
- + this._panel.style.removeProperty("width");
- + this._panel.style.removeProperty("height");
- +
- + // We're setting the width property to prevent flickering during the
- + // sliding animation with smaller views.
- + viewNode.style.width = viewRect.width + "px";
- +
- + // Kick off the transition!
- + details.phase = TRANSITION_PHASES.TRANSITION;
- +
- + // If we're going to show the main view, we can remove the
- + // min-height property on the view container.
- + if (viewNode.getAttribute("mainview")) {
- + this._viewContainer.style.removeProperty("min-height");
- + }
- +
- + this._viewStack.style.transform =
- + "translateX(" + (moveToLeft ? "" : "-") + deltaX + "px)";
- +
- + await new Promise(resolve => {
- + details.resolve = resolve;
- + this._viewContainer.addEventListener(
- + "transitionend",
- + (details.listener = ev => {
- + // It's quite common that `height` on the view container doesn't need
- + // to transition, so we make sure to do all the work on the transform
- + // transition-end, because that is guaranteed to happen.
- + if (ev.target != this._viewStack || ev.propertyName != "transform") {
- + return;
- + }
- + this._viewContainer.removeEventListener(
- + "transitionend",
- + details.listener
- + );
- + delete details.listener;
- + resolve();
- + })
- + );
- + this._viewContainer.addEventListener(
- + "transitioncancel",
- + (details.cancelListener = ev => {
- + if (ev.target != this._viewStack) {
- + return;
- + }
- + this._viewContainer.removeEventListener(
- + "transitioncancel",
- + details.cancelListener
- + );
- + delete details.cancelListener;
- + resolve();
- + })
- + );
- + });
- +
- + // Bail out if the panel was closed during the transition.
- + if (!nextPanelView.isOpenIn(this)) {
- + return;
- + }
- + prevPanelView.visible = false;
- +
- + // This will complete the operation by removing any transition properties.
- + nextPanelView.node.style.removeProperty("width");
- + deepestNode.style.removeProperty("outline");
- + this._cleanupTransitionPhase();
- +
- + nextPanelView.focusSelectedElement();
- + }
- +
- + /**
- + * Attempt to clean up the attributes and properties set by `_transitionViews`
- + * above. Which attributes and properties depends on the phase the transition
- + * was left from.
- + */
- + _cleanupTransitionPhase() {
- + if (!this._transitionDetails) {
- + return;
- + }
- +
- + const { phase, resolve, listener, cancelListener } =
- + this._transitionDetails;
- + this._transitionDetails = null;
- +
- + if (phase >= TRANSITION_PHASES.START) {
- + this._panel.style.removeProperty("width");
- + this._panel.style.removeProperty("height");
- + this._viewContainer.style.removeProperty("height");
- + this._viewContainer.style.removeProperty("width");
- + }
- + if (phase >= TRANSITION_PHASES.PREPARE) {
- + this._transitioning = false;
- + this._viewStack.style.removeProperty("margin-inline-start");
- + this._viewStack.style.removeProperty("transition");
- + }
- + if (phase >= TRANSITION_PHASES.TRANSITION) {
- + this._viewStack.style.removeProperty("transform");
- + if (listener) {
- + this._viewContainer.removeEventListener("transitionend", listener);
- + }
- + if (cancelListener) {
- + this._viewContainer.removeEventListener(
- + "transitioncancel",
- + cancelListener
- + );
- + }
- + if (resolve) {
- + resolve();
- + }
- + }
- + }
- +
- + _calculateMaxHeight(aEvent) {
- + // While opening the panel, we have to limit the maximum height of any
- + // view based on the space that will be available. We cannot just use
- + // window.screen.availTop and availHeight because these may return an
- + // incorrect value when the window spans multiple screens.
- + const anchor = this._panel.anchorNode;
- + const anchorRect = anchor.getBoundingClientRect();
- +
- + const screen = this._screenManager.screenForRect(
- + anchor.screenX,
- + anchor.screenY,
- + anchorRect.width,
- + anchorRect.height
- + );
- + const availTop = {},
- + availHeight = {};
- + screen.GetAvailRect({}, availTop, {}, availHeight);
- + const cssAvailTop = availTop.value / screen.defaultCSSScaleFactor;
- +
- + // The distance from the anchor to the available margin of the screen is
- + // based on whether the panel will open towards the top or the bottom.
- + let maxHeight;
- + if (aEvent.alignmentPosition.startsWith("before_")) {
- + maxHeight = anchor.screenY - cssAvailTop;
- + } else {
- + const anchorScreenBottom = anchor.screenY + anchorRect.height;
- + const cssAvailHeight = availHeight.value / screen.defaultCSSScaleFactor;
- + maxHeight = cssAvailTop + cssAvailHeight - anchorScreenBottom;
- + }
- +
- + // To go from the maximum height of the panel to the maximum height of
- + // the view stack, we need to subtract the height of the arrow and the
- + // height of the opposite margin, but we cannot get their actual values
- + // because the panel is not visible yet. However, we know that this is
- + // currently 11px on Mac, 13px on Windows, and 13px on Linux. We also
- + // want an extra margin, both for visual reasons and to prevent glitches
- + // due to small rounding errors. So, we just use a value that makes
- + // sense for all platforms. If the arrow visuals change significantly,
- + // this value will be easy to adjust.
- + const EXTRA_MARGIN_PX = 20;
- + maxHeight -= EXTRA_MARGIN_PX;
- + return maxHeight;
- + }
- +
- + handleEvent(aEvent) {
- + // Only process actual popup events from the panel or events we generate
- + // ourselves, but not from menus being shown from within the panel.
- + if (
- + aEvent.type.startsWith("popup") &&
- + aEvent.target != this._panel &&
- + aEvent.target != this.node
- + ) {
- + return;
- + }
- + switch (aEvent.type) {
- + case "keydown":
- + // Since we start listening for the "keydown" event when the popup is
- + // already showing and stop listening when the panel is hidden, we
- + // always have at least one view open.
- + const currentView = this.openViews[this.openViews.length - 1];
- + currentView.keyNavigation(aEvent);
- + break;
- + case "mousemove":
- + this.openViews.forEach(panelView => panelView.clearNavigation());
- + break;
- + case "popupshowing": {
- + this._viewContainer.setAttribute("panelopen", "true");
- + if (!this.node.hasAttribute("disablekeynav")) {
- + // We add the keydown handler on the window so that it handles key
- + // presses when a panel appears but doesn't get focus, as happens
- + // when a button to open a panel is clicked with the mouse.
- + // However, this means the listener is on an ancestor of the panel,
- + // which means that handlers such as ToolbarKeyboardNavigator are
- + // deeper in the tree. Therefore, this must be a capturing listener
- + // so we get the event first.
- + this.window.addEventListener("keydown", this, true);
- + this._panel.addEventListener("mousemove", this);
- + }
- + break;
- + }
- + case "popuppositioned": {
- + if (this._panel.state == "showing") {
- + const maxHeight = this._calculateMaxHeight(aEvent);
- + this._viewStack.style.maxHeight = maxHeight + "px";
- + this._offscreenViewStack.style.maxHeight = maxHeight + "px";
- + }
- + break;
- + }
- + case "popupshown":
- + // The main view is always open and visible when the panel is first
- + // shown, so we can check the height of the description elements it
- + // contains and notify consumers using the ViewShown event. In order to
- + // minimize flicker we need to allow synchronous reflows, and we still
- + // make sure the ViewShown event is dispatched synchronously.
- + const mainPanelView = this.openViews[0];
- + this._activateView(mainPanelView);
- + break;
- + case "popuphidden": {
- + // WebExtensions consumers can hide the popup from viewshowing, or
- + // mid-transition, which disrupts our state:
- + this._transitioning = false;
- + this._viewContainer.removeAttribute("panelopen");
- + this._cleanupTransitionPhase();
- + this.window.removeEventListener("keydown", this, true);
- + this._panel.removeEventListener("mousemove", this);
- + this.closeAllViews();
- +
- + // Clear the main view size caches. The dimensions could be different
- + // when the popup is opened again, e.g. through touch mode sizing.
- + this._viewContainer.style.removeProperty("min-height");
- + this._viewStack.style.removeProperty("max-height");
- + this._viewContainer.style.removeProperty("width");
- + this._viewContainer.style.removeProperty("height");
- +
- + this.dispatchCustomEvent("PanelMultiViewHidden");
- + break;
- + }
- + }
- + }
- +}
- +
- +/**
- + * This is associated to <panelview> elements.
- + */
- +export class PanelView extends AssociatedToNode {
- + constructor(node) {
- + super(node);
- +
- + /**
- + * Indicates whether the view is active. When this is false, consumers can
- + * wait for the ViewShown event to know when the view becomes active.
- + */
- + this.active = false;
- +
- + /**
- + * Specifies whether the view should be focused when active. When this
- + * is true, the first navigable element in the view will be focused
- + * when the view becomes active. This should be set to true when the view
- + * is activated from the keyboard. It will be set to false once the view
- + * is active.
- + */
- + this.focusWhenActive = false;
- + }
- +
- + /**
- + * Indicates whether the view is open in the specified PanelMultiView object.
- + */
- + isOpenIn(panelMultiView) {
- + return this.node.panelMultiView == panelMultiView.node;
- + }
- +
- + /**
- + * The "mainview" attribute is set before the panel is opened when this view
- + * is displayed as the main view, and is removed before the <panelview> is
- + * displayed as a subview. The same view element can be displayed as a main
- + * view and as a subview at different times.
- + */
- + set mainview(value) {
- + if (value) {
- + this.node.setAttribute("mainview", true);
- + } else {
- + this.node.removeAttribute("mainview");
- + }
- + }
- +
- + /**
- + * Determines whether the view is visible. Setting this to false also resets
- + * the "active" property.
- + */
- + set visible(value) {
- + if (value) {
- + this.node.setAttribute("visible", true);
- + } else {
- + this.node.removeAttribute("visible");
- + this.active = false;
- + this.focusWhenActive = false;
- + }
- + }
- +
- + /**
- + * Constrains the width of this view using the "min-width" and "max-width"
- + * styles. Setting this to zero removes the constraints.
- + */
- + set minMaxWidth(value) {
- + const style = this.node.style;
- + if (value) {
- + style.minWidth = style.maxWidth = value + "px";
- + } else {
- + style.removeProperty("min-width");
- + style.removeProperty("max-width");
- + }
- + }
- +
- + /**
- + * Adds a header with the given title, or removes it if the title is empty.
- + */
- + set headerText(value) {
- + // If the header already exists, update or remove it as requested.
- + let header = this.node.firstElementChild;
- + if (header && header.classList.contains("panel-header")) {
- + if (value) {
- + header.querySelector(".panel-header > h1 > span").textContent = value;
- + } else {
- + header.remove();
- + }
- + return;
- + }
- +
- + // The header doesn't exist, only create it if needed.
- + if (!value) {
- + return;
- + }
- +
- + header = this.document.createXULElement("box");
- + header.classList.add("panel-header");
- +
- + const backButton = this.document.createXULElement("toolbarbutton");
- + backButton.className =
- + "subviewbutton subviewbutton-iconic subviewbutton-back";
- + backButton.setAttribute("closemenu", "none");
- + backButton.setAttribute("tabindex", "0");
- +
- + backButton.setAttribute(
- + "aria-label",
- + lazy.gBundle.GetStringFromName("panel.back")
- + );
- +
- + backButton.addEventListener("command", () => {
- + // The panelmultiview element may change if the view is reused.
- + this.node.panelMultiView.goBack();
- + backButton.blur();
- + });
- +
- + const h1 = this.document.createElement("h1");
- + const span = this.document.createElement("span");
- + span.textContent = value;
- + h1.appendChild(span);
- +
- + header.append(backButton, h1);
- + this.node.prepend(header);
- + }
- +
- + /**
- + * Populates the "knownWidth" and "knownHeight" properties with the current
- + * dimensions of the view. These may be zero if the view is invisible.
- + *
- + * These values are relevant during transitions and are retained for backwards
- + * navigation if the view is still open but is invisible.
- + */
- + captureKnownSize() {
- + const rect = this._getBoundsWithoutFlushing(this.node);
- + this.knownWidth = rect.width;
- + this.knownHeight = rect.height;
- + }
- +
- + /**
- + * Determine whether an element can only be navigated to with tab/shift+tab,
- + * not the arrow keys.
- + */
- + _isNavigableWithTabOnly(element) {
- + const tag = element.localName;
- + return (
- + tag == "menulist" ||
- + tag == "input" ||
- + tag == "textarea" ||
- + // Allow tab to reach embedded documents in extension panels.
- + tag == "browser"
- + );
- + }
- +
- + /**
- + * Make a TreeWalker for keyboard navigation.
- + *
- + * @param {boolean} arrowKey If `true`, elements only navigable with tab are
- + * excluded.
- + */
- + _makeNavigableTreeWalker(arrowKey) {
- + const filter = node => {
- + if (node.disabled) {
- + return NodeFilter.FILTER_REJECT;
- + }
- + const bounds = this._getBoundsWithoutFlushing(node);
- + if (bounds.width == 0 || bounds.height == 0) {
- + return NodeFilter.FILTER_REJECT;
- + }
- + if (
- + node.tagName == "button" ||
- + node.tagName == "toolbarbutton" ||
- + node.classList.contains("text-link") ||
- + (!arrowKey && this._isNavigableWithTabOnly(node))
- + ) {
- + // Set the tabindex attribute to make sure the node is focusable.
- + if (!node.hasAttribute("tabindex")) {
- + node.setAttribute("tabindex", "-1");
- + }
- + return NodeFilter.FILTER_ACCEPT;
- + }
- + return NodeFilter.FILTER_SKIP;
- + };
- + return this.document.createTreeWalker(
- + this.node,
- + NodeFilter.SHOW_ELEMENT,
- + filter
- + );
- + }
- +
- + /**
- + * Get a TreeWalker which finds elements navigable with tab/shift+tab.
- + */
- + get _tabNavigableWalker() {
- + if (!this.__tabNavigableWalker) {
- + this.__tabNavigableWalker = this._makeNavigableTreeWalker(false);
- + }
- + return this.__tabNavigableWalker;
- + }
- +
- + /**
- + * Get a TreeWalker which finds elements navigable with up/down arrow keys.
- + */
- + get _arrowNavigableWalker() {
- + if (!this.__arrowNavigableWalker) {
- + this.__arrowNavigableWalker = this._makeNavigableTreeWalker(true);
- + }
- + return this.__arrowNavigableWalker;
- + }
- +
- + /**
- + * Element that is currently selected with the keyboard, or null if no element
- + * is selected. Since the reference is held weakly, it can become null or
- + * undefined at any time.
- + */
- + get selectedElement() {
- + return this._selectedElement && this._selectedElement.get();
- + }
- + set selectedElement(value) {
- + if (!value) {
- + delete this._selectedElement;
- + } else {
- + this._selectedElement = Cu.getWeakReference(value);
- + }
- + }
- +
- + /**
- + * Focuses and moves keyboard selection to the first navigable element.
- + * This is a no-op if there are no navigable elements.
- + *
- + * @param {boolean} homeKey - `true` if this is for the home key.
- + * @param {boolean} skipBack - `true` if the Back button should be skipped.
- + */
- + focusFirstNavigableElement(homeKey = false, skipBack = false) {
- + // The home key is conceptually similar to the up/down arrow keys.
- + const walker = homeKey
- + ? this._arrowNavigableWalker
- + : this._tabNavigableWalker;
- + walker.currentNode = walker.root;
- + this.selectedElement = walker.firstChild();
- + if (
- + skipBack &&
- + walker.currentNode &&
- + walker.currentNode.classList.contains("subviewbutton-back") &&
- + walker.nextNode()
- + ) {
- + this.selectedElement = walker.currentNode;
- + }
- + this.focusSelectedElement(/* byKey */ true);
- + }
- +
- + /**
- + * Focuses and moves keyboard selection to the last navigable element.
- + * This is a no-op if there are no navigable elements.
- + *
- + * @param {boolean} endKey - `true` if this is for the end key.
- + */
- + focusLastNavigableElement(endKey = false) {
- + // The end key is conceptually similar to the up/down arrow keys.
- + const walker = endKey
- + ? this._arrowNavigableWalker
- + : this._tabNavigableWalker;
- + walker.currentNode = walker.root;
- + this.selectedElement = walker.lastChild();
- + this.focusSelectedElement(/* byKey */ true);
- + }
- +
- + /**
- + * Based on going up or down, select the previous or next focusable element.
- + *
- + * @param {boolean} isDown - whether we're going down (true) or up (false).
- + * @param {boolean} arrowKey - `true` if this is for the up/down arrow keys.
- + *
- + * @returns {DOMNode} the element we selected.
- + */
- + moveSelection(isDown, arrowKey = false) {
- + const walker = arrowKey
- + ? this._arrowNavigableWalker
- + : this._tabNavigableWalker;
- + const oldSel = this.selectedElement;
- + let newSel;
- + if (oldSel) {
- + walker.currentNode = oldSel;
- + newSel = isDown ? walker.nextNode() : walker.previousNode();
- + }
- + // If we couldn't find something, select the first or last item:
- + if (!newSel) {
- + walker.currentNode = walker.root;
- + newSel = isDown ? walker.firstChild() : walker.lastChild();
- + }
- + this.selectedElement = newSel;
- + return newSel;
- + }
- +
- + /**
- + * Allow for navigating subview buttons using the arrow keys and the Enter key.
- + * The Up and Down keys can be used to navigate the list up and down and the
- + * Enter, Right or Left - depending on the text direction - key can be used to
- + * simulate a click on the currently selected button.
- + * The Right or Left key - depending on the text direction - can be used to
- + * navigate to the previous view, functioning as a shortcut for the view's
- + * back button.
- + * Thus, in LTR mode:
- + * - The Right key functions the same as the Enter key, simulating a click
- + * - The Left key triggers a navigation back to the previous view.
- + *
- + * Key navigation is only enabled while the view is active, meaning that this
- + * method will return early if it is invoked during a sliding transition.
- + *
- + * @param {KeyEvent} event
- + */
- + /* eslint-disable-next-line complexity */
- + keyNavigation(event) {
- + if (!this.active) {
- + return;
- + }
- +
- + let focus = this.document.activeElement;
- + // Make sure the focus is actually inside the panel. (It might not be if
- + // the panel was opened with the mouse.) If it isn't, we don't care
- + // about it for our purposes.
- + // We use Node.compareDocumentPosition because Node.contains doesn't
- + // behave as expected for anonymous content; e.g. the input inside a
- + // textbox.
- + if (
- + focus &&
- + !(
- + this.node.compareDocumentPosition(focus) &
- + Node.DOCUMENT_POSITION_CONTAINED_BY
- + )
- + ) {
- + focus = null;
- + }
- +
- + // Extension panels contain embedded documents. We can't manage
- + // keyboard navigation within those.
- + if (focus && focus.tagName == "browser") {
- + return;
- + }
- +
- + const stop = () => {
- + event.stopPropagation();
- + event.preventDefault();
- + };
- +
- + // If the focused element is only navigable with tab, it wants the arrow
- + // keys, etc. We shouldn't handle any keys except tab and shift+tab.
- + // We make a function for this for performance reasons: we only want to
- + // check this for keys we potentially care about, not *all* keys.
- + const tabOnly = () => {
- + // We use the real focus rather than this.selectedElement because focus
- + // might have been moved without keyboard navigation (e.g. mouse click)
- + // and this.selectedElement is only updated for keyboard navigation.
- + return focus && this._isNavigableWithTabOnly(focus);
- + };
- +
- + // If a context menu is open, we must let it handle all keys.
- + // Normally, this just happens, but because we have a capturing window
- + // keydown listener, our listener takes precedence.
- + // Again, we only want to do this check on demand for performance.
- + const isContextMenuOpen = () => {
- + if (!focus) {
- + return false;
- + }
- + const contextNode = focus.closest("[context]");
- + if (!contextNode) {
- + return false;
- + }
- + const context = contextNode.getAttribute("context");
- + const popup = this.document.getElementById(context);
- + return popup && popup.state == "open";
- + };
- +
- + const keyCode = event.code;
- + switch (keyCode) {
- + case "ArrowDown":
- + case "ArrowUp":
- + if (tabOnly()) {
- + break;
- + }
- + // Fall-through...
- + case "Tab": {
- + if (isContextMenuOpen()) {
- + break;
- + }
- + stop();
- + const isDown =
- + keyCode == "ArrowDown" || (keyCode == "Tab" && !event.shiftKey);
- + const button = this.moveSelection(isDown, keyCode != "Tab");
- + Services.focus.setFocus(button, Services.focus.FLAG_BYKEY);
- + break;
- + }
- + case "Home":
- + if (tabOnly() || isContextMenuOpen()) {
- + break;
- + }
- + stop();
- + this.focusFirstNavigableElement(true);
- + break;
- + case "End":
- + if (tabOnly() || isContextMenuOpen()) {
- + break;
- + }
- + stop();
- + this.focusLastNavigableElement(true);
- + break;
- + case "ArrowLeft":
- + case "ArrowRight": {
- + if (tabOnly() || isContextMenuOpen()) {
- + break;
- + }
- + stop();
- + if (
- + (!this.window.RTL_UI && keyCode == "ArrowLeft") ||
- + (this.window.RTL_UI && keyCode == "ArrowRight")
- + ) {
- + this.node.panelMultiView.goBack();
- + break;
- + }
- + // If the current button is _not_ one that points to a subview, pressing
- + // the arrow key shouldn't do anything.
- + const button = this.selectedElement;
- + if (!button || !button.classList.contains("subviewbutton-nav")) {
- + break;
- + }
- + }
- + // Fall-through...
- + case "Space":
- + case "NumpadEnter":
- + case "Enter": {
- + if (tabOnly() || isContextMenuOpen()) {
- + break;
- + }
- + const button = this.selectedElement;
- + if (!button) {
- + break;
- + }
- + stop();
- +
- + this._doingKeyboardActivation = true;
- + // Unfortunately, 'tabindex' doesn't execute the default action, so
- + // we explicitly do this here.
- + // We are sending a command event, a mousedown event and then a click
- + // event. This is done in order to mimic a "real" mouse click event.
- + // Normally, the command event executes the action, then the click event
- + // closes the menu. However, in some cases (e.g. the Library button),
- + // there is no command event handler and the mousedown event executes the
- + // action instead.
- + button.doCommand();
- + let dispEvent = new event.target.ownerGlobal.MouseEvent("mousedown", {
- + bubbles: true,
- + });
- + button.dispatchEvent(dispEvent);
- + dispEvent = new event.target.ownerGlobal.MouseEvent("click", {
- + bubbles: true,
- + });
- + button.dispatchEvent(dispEvent);
- + this._doingKeyboardActivation = false;
- + break;
- + }
- + }
- + }
- +
- + /**
- + * Focus the last selected element in the view, if any.
- + *
- + * @param byKey {Boolean} whether focus was moved by the user pressing a key.
- + * Needed to ensure we show focus styles in the right cases.
- + */
- + focusSelectedElement(byKey = false) {
- + const selected = this.selectedElement;
- + if (selected) {
- + const flag = byKey ? "FLAG_BYKEY" : "FLAG_BYELEMENTFOCUS";
- + Services.focus.setFocus(selected, Services.focus[flag]);
- + }
- + }
- +
- + /**
- + * Clear all traces of keyboard navigation happening right now.
- + */
- + clearNavigation() {
- + const selected = this.selectedElement;
- + if (selected) {
- + selected.blur();
- + this.selectedElement = null;
- + }
- + }
- +}
- diff --git a/suite/buoy/modules/SessionStore.sys.mjs b/suite/buoy/modules/SessionStore.sys.mjs
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/modules/SessionStore.sys.mjs
- @@ -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 is a shim for SessionStore in moz-central to prevent bug 1713801. Only
- + * the methods that appear to be hit by comm-central are implemented.
- + */
- +export var SessionStore = {
- + updateSessionStoreFromTablistener(aBrowser, aBrowsingContext, aData) {},
- + maybeExitCrashedState() {},
- +};
- diff --git a/suite/buoy/moz-l10n/browser/appExtensionFields.ftl b/suite/buoy/moz-l10n/browser/appExtensionFields.ftl
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/moz-l10n/browser/appExtensionFields.ftl
- @@ -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/.
- +
- +## Theme names and descriptions used in the Themes panel in about:addons
- +
- +# "Auto" is short for automatic. It can be localized without limitations.
- +extension-default-theme-name-auto=System theme — auto
- +extension-default-theme-description=Follow the operating system setting for buttons, menus, and windows.
- +
- diff --git a/suite/buoy/moz-l10n/browser/branding/brandings.ftl b/suite/buoy/moz-l10n/browser/branding/brandings.ftl
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/moz-l10n/browser/branding/brandings.ftl
- @@ -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/.
- +
- +## The following feature names must be treated as a brand.
- +##
- +## They cannot be:
- +## - Transliterated.
- +## - Translated.
- +##
- +## Declension should be avoided where possible, leaving the original
- +## brand unaltered in prominent UI positions.
- +##
- +## For further details, consult:
- +## https://mozilla-l10n.github.io/styleguides/mozilla_general/#brands-copyright-and-trademark
- +
- +-profiler-brand-name = Firefox Profiler
- diff --git a/suite/buoy/moz.build b/suite/buoy/moz.build
- new file mode 100644
- --- /dev/null
- +++ b/suite/buoy/moz.build
- @@ -0,0 +1,15 @@
- +EXTRA_JS_MODULES += [
- + "modules/BrowserWindowTracker.sys.mjs",
- + "modules/CustomizableUI.sys.mjs",
- + "modules/EzE10SUtils.sys.mjs",
- + "modules/PanelMultiView.sys.mjs",
- +]
- +
- +EXTRA_JS_MODULES.sessionstore += [
- + "modules/SessionStore.sys.mjs",
- +]
- +
- +
- +JS_PREFERENCE_FILES += ["ZZ-buoy-prefs.js"]
- +
- +JAR_MANIFESTS += ["jar.mn"]
- diff --git a/suite/moz.build b/suite/moz.build
- --- a/suite/moz.build
- +++ b/suite/moz.build
- @@ -24,16 +24,19 @@ DIRS += [
- if CONFIG["MOZ_THUNDERBIRD_RUST"]:
- DEFINES["MOZ_THUNDERBIRD_RUST"] = 1
-
- if CONFIG["MOZ_OVERRIDE_GKRUST"]:
- DIRS += [
- "../rust",
- ]
-
- +if CONFIG['MOZ_SUITE_BUOY']:
- + DIRS += ['buoy']
- +
- if CONFIG['MOZ_IRC']:
- DIRS += ['chatzilla']
-
- if CONFIG["MAKENSISU"]:
- DIRS += ["installer/windows"]
-
- if CONFIG["MOZ_BUNDLED_FONTS"]:
- DIRS += ["/browser/fonts"]
- diff --git a/suite/moz.configure b/suite/moz.configure
- --- a/suite/moz.configure
- +++ b/suite/moz.configure
- @@ -99,16 +99,29 @@ def moz_override_cargo_config(enable_rus
-
-
- set_config(
- "MOZ_OVERRIDE_CARGO_CONFIG",
- moz_override_cargo_config,
- when="--enable-thunderbird-rust",
- )
-
- +# =========================================================
- +# = Diagnostic "Buoy" Component
- +# =========================================================
- +option(
- + "--enable-buoy", default=False, help="Enable building of the SeaMonkey Diagnostic Component"
- +)
- +
- +@depends_if("--enable-buoy")
- +def buoy(arg):
- + return True
- +
- +set_config("MOZ_SUITE_BUOY", buoy)
- +
- # Building extensions is disabled by default.
-
- # =========================================================
- # = ChatZilla extension
- # =========================================================
- option(
- "--enable-irc", default=False, help="Enable building of the ChatZilla IRC extension"
- )
|