123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484 |
- /* vim:set ts=2 sw=2 sts=2 et:
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
- /*
- * Original version history can be found here:
- * https://github.com/mozilla/workspace
- *
- * Copied and relicensed from the Public Domain.
- * See bug 653934 for details.
- * https://bugzilla.mozilla.org/show_bug.cgi?id=653934
- */
- "use strict";
- var Cu = Components.utils;
- var Cc = Components.classes;
- var Ci = Components.interfaces;
- const SCRATCHPAD_CONTEXT_CONTENT = 1;
- const SCRATCHPAD_CONTEXT_BROWSER = 2;
- const BUTTON_POSITION_SAVE = 0;
- const BUTTON_POSITION_CANCEL = 1;
- const BUTTON_POSITION_DONT_SAVE = 2;
- const BUTTON_POSITION_REVERT = 0;
- const EVAL_FUNCTION_TIMEOUT = 1000; // milliseconds
- const MAXIMUM_FONT_SIZE = 96;
- const MINIMUM_FONT_SIZE = 6;
- const NORMAL_FONT_SIZE = 12;
- const SCRATCHPAD_L10N = "chrome://devtools/locale/scratchpad.properties";
- const DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
- const PREF_RECENT_FILES_MAX = "devtools.scratchpad.recentFilesMax";
- const SHOW_LINE_NUMBERS = "devtools.scratchpad.lineNumbers";
- const WRAP_TEXT = "devtools.scratchpad.wrapText";
- const SHOW_TRAILING_SPACE = "devtools.scratchpad.showTrailingSpace";
- const EDITOR_FONT_SIZE = "devtools.scratchpad.editorFontSize";
- const ENABLE_AUTOCOMPLETION = "devtools.scratchpad.enableAutocompletion";
- const TAB_SIZE = "devtools.editor.tabsize";
- const FALLBACK_CHARSET_LIST = "intl.fallbackCharsetList.ISO-8859-1";
- const VARIABLES_VIEW_URL = "chrome://devtools/content/shared/widgets/VariablesView.xul";
- const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
- const Editor = require("devtools/client/sourceeditor/editor");
- const TargetFactory = require("devtools/client/framework/target").TargetFactory;
- const EventEmitter = require("devtools/shared/event-emitter");
- const {DevToolsWorker} = require("devtools/shared/worker/worker");
- const DevToolsUtils = require("devtools/shared/DevToolsUtils");
- const flags = require("devtools/shared/flags");
- const promise = require("promise");
- const Services = require("Services");
- const {gDevTools} = require("devtools/client/framework/devtools");
- const {Heritage} = require("devtools/client/shared/widgets/view-helpers");
- const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
- const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
- const {ScratchpadManager} = require("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
- const {addDebuggerToGlobal} = require("resource://gre/modules/jsdebugger.jsm");
- const {OS} = require("resource://gre/modules/osfile.jsm");
- const {Reflect} = require("resource://gre/modules/reflect.jsm");
- XPCOMUtils.defineConstant(this, "SCRATCHPAD_CONTEXT_CONTENT", SCRATCHPAD_CONTEXT_CONTENT);
- XPCOMUtils.defineConstant(this, "SCRATCHPAD_CONTEXT_BROWSER", SCRATCHPAD_CONTEXT_BROWSER);
- XPCOMUtils.defineConstant(this, "BUTTON_POSITION_SAVE", BUTTON_POSITION_SAVE);
- XPCOMUtils.defineConstant(this, "BUTTON_POSITION_CANCEL", BUTTON_POSITION_CANCEL);
- XPCOMUtils.defineConstant(this, "BUTTON_POSITION_DONT_SAVE", BUTTON_POSITION_DONT_SAVE);
- XPCOMUtils.defineConstant(this, "BUTTON_POSITION_REVERT", BUTTON_POSITION_REVERT);
- XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
- "resource://devtools/client/shared/widgets/VariablesView.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "VariablesViewController",
- "resource://devtools/client/shared/widgets/VariablesViewController.jsm");
- loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
- loader.lazyRequireGetter(this, "DebuggerClient", "devtools/shared/client/main", true);
- loader.lazyRequireGetter(this, "EnvironmentClient", "devtools/shared/client/main", true);
- loader.lazyRequireGetter(this, "ObjectClient", "devtools/shared/client/main", true);
- loader.lazyRequireGetter(this, "HUDService", "devtools/client/webconsole/hudservice", true);
- XPCOMUtils.defineLazyGetter(this, "REMOTE_TIMEOUT", () =>
- Services.prefs.getIntPref("devtools.debugger.remote-timeout"));
- XPCOMUtils.defineLazyModuleGetter(this, "ShortcutUtils",
- "resource://gre/modules/ShortcutUtils.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "Reflect",
- "resource://gre/modules/reflect.jsm");
- var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
- /**
- * The scratchpad object handles the Scratchpad window functionality.
- */
- var Scratchpad = {
- _instanceId: null,
- _initialWindowTitle: document.title,
- _dirty: false,
- /**
- * Check if provided string is a mode-line and, if it is, return an
- * object with its values.
- *
- * @param string aLine
- * @return string
- */
- _scanModeLine: function SP__scanModeLine(aLine = "")
- {
- aLine = aLine.trim();
- let obj = {};
- let ch1 = aLine.charAt(0);
- let ch2 = aLine.charAt(1);
- if (ch1 !== "/" || (ch2 !== "*" && ch2 !== "/")) {
- return obj;
- }
- aLine = aLine
- .replace(/^\/\//, "")
- .replace(/^\/\*/, "")
- .replace(/\*\/$/, "");
- aLine.split(",").forEach(pair => {
- let [key, val] = pair.split(":");
- if (key && val) {
- obj[key.trim()] = val.trim();
- }
- });
- return obj;
- },
- /**
- * Add the event listeners for popupshowing events.
- */
- _setupPopupShowingListeners: function SP_setupPopupShowing() {
- let elementIDs = ["sp-menu_editpopup", "scratchpad-text-popup"];
- for (let elementID of elementIDs) {
- let elem = document.getElementById(elementID);
- if (elem) {
- elem.addEventListener("popupshowing", function () {
- goUpdateGlobalEditMenuItems();
- let commands = ["cmd_undo", "cmd_redo", "cmd_delete", "cmd_findAgain"];
- commands.forEach(goUpdateCommand);
- });
- }
- }
- },
- /**
- * Add the event event listeners for command events.
- */
- _setupCommandListeners: function SP_setupCommands() {
- let commands = {
- "cmd_find": () => {
- goDoCommand("cmd_find");
- },
- "cmd_findAgain": () => {
- goDoCommand("cmd_findAgain");
- },
- "cmd_gotoLine": () => {
- goDoCommand("cmd_gotoLine");
- },
- "sp-cmd-newWindow": () => {
- Scratchpad.openScratchpad();
- },
- "sp-cmd-openFile": () => {
- Scratchpad.openFile();
- },
- "sp-cmd-clearRecentFiles": () => {
- Scratchpad.clearRecentFiles();
- },
- "sp-cmd-save": () => {
- Scratchpad.saveFile();
- },
- "sp-cmd-saveas": () => {
- Scratchpad.saveFileAs();
- },
- "sp-cmd-revert": () => {
- Scratchpad.promptRevert();
- },
- "sp-cmd-close": () => {
- Scratchpad.close();
- },
- "sp-cmd-run": () => {
- Scratchpad.run();
- },
- "sp-cmd-inspect": () => {
- Scratchpad.inspect();
- },
- "sp-cmd-display": () => {
- Scratchpad.display();
- },
- "sp-cmd-pprint": () => {
- Scratchpad.prettyPrint();
- },
- "sp-cmd-contentContext": () => {
- Scratchpad.setContentContext();
- },
- "sp-cmd-browserContext": () => {
- Scratchpad.setBrowserContext();
- },
- "sp-cmd-reloadAndRun": () => {
- Scratchpad.reloadAndRun();
- },
- "sp-cmd-evalFunction": () => {
- Scratchpad.evalTopLevelFunction();
- },
- "sp-cmd-errorConsole": () => {
- Scratchpad.openErrorConsole();
- },
- "sp-cmd-webConsole": () => {
- Scratchpad.openWebConsole();
- },
- "sp-cmd-documentationLink": () => {
- Scratchpad.openDocumentationPage();
- },
- "sp-cmd-hideSidebar": () => {
- Scratchpad.sidebar.hide();
- },
- "sp-cmd-line-numbers": () => {
- Scratchpad.toggleEditorOption("lineNumbers", SHOW_LINE_NUMBERS);
- },
- "sp-cmd-wrap-text": () => {
- Scratchpad.toggleEditorOption("lineWrapping", WRAP_TEXT);
- },
- "sp-cmd-highlight-trailing-space": () => {
- Scratchpad.toggleEditorOption("showTrailingSpace", SHOW_TRAILING_SPACE);
- },
- "sp-cmd-larger-font": () => {
- Scratchpad.increaseFontSize();
- },
- "sp-cmd-smaller-font": () => {
- Scratchpad.decreaseFontSize();
- },
- "sp-cmd-normal-font": () => {
- Scratchpad.normalFontSize();
- },
- };
- for (let command in commands) {
- let elem = document.getElementById(command);
- if (elem) {
- elem.addEventListener("command", commands[command]);
- }
- }
- },
- /**
- * Check or uncheck view menu items according to stored preferences.
- */
- _updateViewMenuItems: function SP_updateViewMenuItems() {
- this._updateViewMenuItem(SHOW_LINE_NUMBERS, "sp-menu-line-numbers");
- this._updateViewMenuItem(WRAP_TEXT, "sp-menu-word-wrap");
- this._updateViewMenuItem(SHOW_TRAILING_SPACE, "sp-menu-highlight-trailing-space");
- this._updateViewFontMenuItem(MINIMUM_FONT_SIZE, "sp-cmd-smaller-font");
- this._updateViewFontMenuItem(MAXIMUM_FONT_SIZE, "sp-cmd-larger-font");
- },
- /**
- * Check or uncheck view menu item according to stored preferences.
- */
- _updateViewMenuItem: function SP_updateViewMenuItem(preferenceName, menuId) {
- let checked = Services.prefs.getBoolPref(preferenceName);
- if (checked) {
- document.getElementById(menuId).setAttribute("checked", true);
- } else {
- document.getElementById(menuId).removeAttribute("checked");
- }
- },
- /**
- * Disable view menu item if the stored font size is equals to the given one.
- */
- _updateViewFontMenuItem: function SP_updateViewFontMenuItem(fontSize, commandId) {
- let prefFontSize = Services.prefs.getIntPref(EDITOR_FONT_SIZE);
- if (prefFontSize === fontSize) {
- document.getElementById(commandId).setAttribute("disabled", true);
- }
- },
- /**
- * The script execution context. This tells Scratchpad in which context the
- * script shall execute.
- *
- * Possible values:
- * - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
- * tab content window object.
- * - SCRATCHPAD_CONTEXT_BROWSER to execute code in the context of the
- * currently active chrome window object.
- */
- executionContext: SCRATCHPAD_CONTEXT_CONTENT,
- /**
- * Tells if this Scratchpad is initialized and ready for use.
- * @boolean
- * @see addObserver
- */
- initialized: false,
- /**
- * Returns the 'dirty' state of this Scratchpad.
- */
- get dirty()
- {
- let clean = this.editor && this.editor.isClean();
- return this._dirty || !clean;
- },
- /**
- * Sets the 'dirty' state of this Scratchpad.
- */
- set dirty(aValue)
- {
- this._dirty = aValue;
- if (!aValue && this.editor)
- this.editor.setClean();
- this._updateTitle();
- },
- /**
- * Retrieve the xul:notificationbox DOM element. It notifies the user when
- * the current code execution context is SCRATCHPAD_CONTEXT_BROWSER.
- */
- get notificationBox()
- {
- return document.getElementById("scratchpad-notificationbox");
- },
- /**
- * Hide the menu bar.
- */
- hideMenu: function SP_hideMenu()
- {
- document.getElementById("sp-menubar").style.display = "none";
- },
- /**
- * Show the menu bar.
- */
- showMenu: function SP_showMenu()
- {
- document.getElementById("sp-menubar").style.display = "";
- },
- /**
- * Get the editor content, in the given range. If no range is given you get
- * the entire editor content.
- *
- * @param number [aStart=0]
- * Optional, start from the given offset.
- * @param number [aEnd=content char count]
- * Optional, end offset for the text you want. If this parameter is not
- * given, then the text returned goes until the end of the editor
- * content.
- * @return string
- * The text in the given range.
- */
- getText: function SP_getText(aStart, aEnd)
- {
- var value = this.editor.getText();
- return value.slice(aStart || 0, aEnd || value.length);
- },
- /**
- * Set the filename in the scratchpad UI and object
- *
- * @param string aFilename
- * The new filename
- */
- setFilename: function SP_setFilename(aFilename)
- {
- this.filename = aFilename;
- this._updateTitle();
- },
- /**
- * Update the Scratchpad window title based on the current state.
- * @private
- */
- _updateTitle: function SP__updateTitle()
- {
- let title = this.filename || this._initialWindowTitle;
- if (this.dirty)
- title = "*" + title;
- document.title = title;
- },
- /**
- * Get the current state of the scratchpad. Called by the
- * Scratchpad Manager for session storing.
- *
- * @return object
- * An object with 3 properties: filename, text, and
- * executionContext.
- */
- getState: function SP_getState()
- {
- return {
- filename: this.filename,
- text: this.getText(),
- executionContext: this.executionContext,
- saved: !this.dirty
- };
- },
- /**
- * Set the filename and execution context using the given state. Called
- * when scratchpad is being restored from a previous session.
- *
- * @param object aState
- * An object with filename and executionContext properties.
- */
- setState: function SP_setState(aState)
- {
- if (aState.filename)
- this.setFilename(aState.filename);
- this.dirty = !aState.saved;
- if (aState.executionContext == SCRATCHPAD_CONTEXT_BROWSER)
- this.setBrowserContext();
- else
- this.setContentContext();
- },
- /**
- * Get the most recent main chrome browser window
- */
- get browserWindow()
- {
- return Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
- },
- /**
- * Get the gBrowser object of the most recent browser window.
- */
- get gBrowser()
- {
- let recentWin = this.browserWindow;
- return recentWin ? recentWin.gBrowser : null;
- },
- /**
- * Unique name for the current Scratchpad instance. Used to distinguish
- * Scratchpad windows between each other. See bug 661762.
- */
- get uniqueName()
- {
- return "Scratchpad/" + this._instanceId;
- },
- /**
- * Sidebar that contains the VariablesView for object inspection.
- */
- get sidebar()
- {
- if (!this._sidebar) {
- this._sidebar = new ScratchpadSidebar(this);
- }
- return this._sidebar;
- },
- /**
- * Replaces context of an editor with provided value (a string).
- * Note: this method is simply a shortcut to editor.setText.
- */
- setText: function SP_setText(value)
- {
- return this.editor.setText(value);
- },
- /**
- * Evaluate a string in the currently desired context, that is either the
- * chrome window or the tab content window object.
- *
- * @param string aString
- * The script you want to evaluate.
- * @return Promise
- * The promise for the script evaluation result.
- */
- evaluate: function SP_evaluate(aString)
- {
- let connection;
- if (this.target) {
- connection = ScratchpadTarget.consoleFor(this.target);
- }
- else if (this.executionContext == SCRATCHPAD_CONTEXT_CONTENT) {
- connection = ScratchpadTab.consoleFor(this.gBrowser.selectedTab);
- }
- else {
- connection = ScratchpadWindow.consoleFor(this.browserWindow);
- }
- let evalOptions = { url: this.uniqueName };
- return connection.then(({ debuggerClient, webConsoleClient }) => {
- let deferred = promise.defer();
- webConsoleClient.evaluateJSAsync(aString, aResponse => {
- this.debuggerClient = debuggerClient;
- this.webConsoleClient = webConsoleClient;
- if (aResponse.error) {
- deferred.reject(aResponse);
- }
- else if (aResponse.exception !== null) {
- deferred.resolve([aString, aResponse]);
- }
- else {
- deferred.resolve([aString, undefined, aResponse.result]);
- }
- }, evalOptions);
- return deferred.promise;
- });
- },
- /**
- * Execute the selected text (if any) or the entire editor content in the
- * current context.
- *
- * @return Promise
- * The promise for the script evaluation result.
- */
- execute: function SP_execute()
- {
- WebConsoleUtils.usageCount++;
- let selection = this.editor.getSelection() || this.getText();
- return this.evaluate(selection);
- },
- /**
- * Execute the selected text (if any) or the entire editor content in the
- * current context.
- *
- * @return Promise
- * The promise for the script evaluation result.
- */
- run: function SP_run()
- {
- let deferred = promise.defer();
- let reject = aReason => deferred.reject(aReason);
- this.execute().then(([aString, aError, aResult]) => {
- let resolve = () => deferred.resolve([aString, aError, aResult]);
- if (aError) {
- this.writeAsErrorComment(aError).then(resolve, reject);
- }
- else {
- this.editor.dropSelection();
- resolve();
- }
- }, reject);
- return deferred.promise;
- },
- /**
- * Execute the selected text (if any) or the entire editor content in the
- * current context. The resulting object is inspected up in the sidebar.
- *
- * @return Promise
- * The promise for the script evaluation result.
- */
- inspect: function SP_inspect()
- {
- let deferred = promise.defer();
- let reject = aReason => deferred.reject(aReason);
- this.execute().then(([aString, aError, aResult]) => {
- let resolve = () => deferred.resolve([aString, aError, aResult]);
- if (aError) {
- this.writeAsErrorComment(aError).then(resolve, reject);
- }
- else {
- this.editor.dropSelection();
- this.sidebar.open(aString, aResult).then(resolve, reject);
- }
- }, reject);
- return deferred.promise;
- },
- /**
- * Reload the current page and execute the entire editor content when
- * the page finishes loading. Note that this operation should be available
- * only in the content context.
- *
- * @return Promise
- * The promise for the script evaluation result.
- */
- reloadAndRun: function SP_reloadAndRun()
- {
- let deferred = promise.defer();
- if (this.executionContext !== SCRATCHPAD_CONTEXT_CONTENT) {
- console.error(this.strings.
- GetStringFromName("scratchpadContext.invalid"));
- return;
- }
- let target = TargetFactory.forTab(this.gBrowser.selectedTab);
- target.once("navigate", () => {
- this.run().then(results => deferred.resolve(results));
- });
- target.makeRemote().then(() => target.activeTab.reload());
- return deferred.promise;
- },
- /**
- * Execute the selected text (if any) or the entire editor content in the
- * current context. The evaluation result is inserted into the editor after
- * the selected text, or at the end of the editor content if there is no
- * selected text.
- *
- * @return Promise
- * The promise for the script evaluation result.
- */
- display: function SP_display()
- {
- let deferred = promise.defer();
- let reject = aReason => deferred.reject(aReason);
- this.execute().then(([aString, aError, aResult]) => {
- let resolve = () => deferred.resolve([aString, aError, aResult]);
- if (aError) {
- this.writeAsErrorComment(aError).then(resolve, reject);
- }
- else if (VariablesView.isPrimitive({ value: aResult })) {
- this._writePrimitiveAsComment(aResult).then(resolve, reject);
- }
- else {
- let objectClient = new ObjectClient(this.debuggerClient, aResult);
- objectClient.getDisplayString(aResponse => {
- if (aResponse.error) {
- reportError("display", aResponse);
- reject(aResponse);
- }
- else {
- this.writeAsComment(aResponse.displayString);
- resolve();
- }
- });
- }
- }, reject);
- return deferred.promise;
- },
- _prettyPrintWorker: null,
- /**
- * Get or create the worker that handles pretty printing.
- */
- get prettyPrintWorker() {
- if (!this._prettyPrintWorker) {
- this._prettyPrintWorker = new DevToolsWorker(
- "resource://devtools/server/actors/pretty-print-worker.js",
- { name: "pretty-print",
- verbose: flags.wantLogging }
- );
- }
- return this._prettyPrintWorker;
- },
- /**
- * Pretty print the source text inside the scratchpad.
- *
- * @return Promise
- * A promise resolved with the pretty printed code, or rejected with
- * an error.
- */
- prettyPrint: function SP_prettyPrint() {
- const uglyText = this.getText();
- const tabsize = Services.prefs.getIntPref(TAB_SIZE);
- return this.prettyPrintWorker.performTask("pretty-print", {
- url: "(scratchpad)",
- indent: tabsize,
- source: uglyText
- }).then(data => {
- this.editor.setText(data.code);
- }).then(null, error => {
- this.writeAsErrorComment({ exception: error });
- throw error;
- });
- },
- /**
- * Parse the text and return an AST. If we can't parse it, write an error
- * comment and return false.
- */
- _parseText: function SP__parseText(aText) {
- try {
- return Reflect.parse(aText);
- } catch (e) {
- this.writeAsErrorComment({ exception: DevToolsUtils.safeErrorString(e) });
- return false;
- }
- },
- /**
- * Determine if the given AST node location contains the given cursor
- * position.
- *
- * @returns Boolean
- */
- _containsCursor: function (aLoc, aCursorPos) {
- // Our line numbers are 1-based, while CodeMirror's are 0-based.
- const lineNumber = aCursorPos.line + 1;
- const columnNumber = aCursorPos.ch;
- if (aLoc.start.line <= lineNumber && aLoc.end.line >= lineNumber) {
- if (aLoc.start.line === aLoc.end.line) {
- return aLoc.start.column <= columnNumber
- && aLoc.end.column >= columnNumber;
- }
- if (aLoc.start.line == lineNumber) {
- return columnNumber >= aLoc.start.column;
- }
- if (aLoc.end.line == lineNumber) {
- return columnNumber <= aLoc.end.column;
- }
- return true;
- }
- return false;
- },
- /**
- * Find the top level function AST node that the cursor is within.
- *
- * @returns Object|null
- */
- _findTopLevelFunction: function SP__findTopLevelFunction(aAst, aCursorPos) {
- for (let statement of aAst.body) {
- switch (statement.type) {
- case "FunctionDeclaration":
- if (this._containsCursor(statement.loc, aCursorPos)) {
- return statement;
- }
- break;
- case "VariableDeclaration":
- for (let decl of statement.declarations) {
- if (!decl.init) {
- continue;
- }
- if ((decl.init.type == "FunctionExpression"
- || decl.init.type == "ArrowFunctionExpression")
- && this._containsCursor(decl.loc, aCursorPos)) {
- return decl;
- }
- }
- break;
- }
- }
- return null;
- },
- /**
- * Get the source text associated with the given function statement.
- *
- * @param Object aFunction
- * @param String aFullText
- * @returns String
- */
- _getFunctionText: function SP__getFunctionText(aFunction, aFullText) {
- let functionText = "";
- // Initially set to 0, but incremented first thing in the loop below because
- // line numbers are 1 based, not 0 based.
- let lineNumber = 0;
- const { start, end } = aFunction.loc;
- const singleLine = start.line === end.line;
- for (let line of aFullText.split(/\n/g)) {
- lineNumber++;
- if (singleLine && start.line === lineNumber) {
- functionText = line.slice(start.column, end.column);
- break;
- }
- if (start.line === lineNumber) {
- functionText += line.slice(start.column) + "\n";
- continue;
- }
- if (end.line === lineNumber) {
- functionText += line.slice(0, end.column);
- break;
- }
- if (start.line < lineNumber && end.line > lineNumber) {
- functionText += line + "\n";
- }
- }
- return functionText;
- },
- /**
- * Evaluate the top level function that the cursor is resting in.
- *
- * @returns Promise [text, error, result]
- */
- evalTopLevelFunction: function SP_evalTopLevelFunction() {
- const text = this.getText();
- const ast = this._parseText(text);
- if (!ast) {
- return promise.resolve([text, undefined, undefined]);
- }
- const cursorPos = this.editor.getCursor();
- const funcStatement = this._findTopLevelFunction(ast, cursorPos);
- if (!funcStatement) {
- return promise.resolve([text, undefined, undefined]);
- }
- let functionText = this._getFunctionText(funcStatement, text);
- // TODO: This is a work around for bug 940086. It should be removed when
- // that is fixed.
- if (funcStatement.type == "FunctionDeclaration"
- && !functionText.startsWith("function ")) {
- functionText = "function " + functionText;
- funcStatement.loc.start.column -= 9;
- }
- // The decrement by one is because our line numbers are 1-based, while
- // CodeMirror's are 0-based.
- const from = {
- line: funcStatement.loc.start.line - 1,
- ch: funcStatement.loc.start.column
- };
- const to = {
- line: funcStatement.loc.end.line - 1,
- ch: funcStatement.loc.end.column
- };
- const marker = this.editor.markText(from, to, "eval-text");
- setTimeout(() => marker.clear(), EVAL_FUNCTION_TIMEOUT);
- return this.evaluate(functionText);
- },
- /**
- * Writes out a primitive value as a comment. This handles values which are
- * to be printed directly (number, string) as well as grips to values
- * (null, undefined, longString).
- *
- * @param any aValue
- * The value to print.
- * @return Promise
- * The promise that resolves after the value has been printed.
- */
- _writePrimitiveAsComment: function SP__writePrimitiveAsComment(aValue)
- {
- let deferred = promise.defer();
- if (aValue.type == "longString") {
- let client = this.webConsoleClient;
- client.longString(aValue).substring(0, aValue.length, aResponse => {
- if (aResponse.error) {
- reportError("display", aResponse);
- deferred.reject(aResponse);
- }
- else {
- deferred.resolve(aResponse.substring);
- }
- });
- }
- else {
- deferred.resolve(aValue.type || aValue);
- }
- return deferred.promise.then(aComment => {
- this.writeAsComment(aComment);
- });
- },
- /**
- * Write out a value at the next line from the current insertion point.
- * The comment block will always be preceded by a newline character.
- * @param object aValue
- * The Object to write out as a string
- */
- writeAsComment: function SP_writeAsComment(aValue)
- {
- let value = "\n/*\n" + aValue + "\n*/";
- if (this.editor.somethingSelected()) {
- let from = this.editor.getCursor("end");
- this.editor.replaceSelection(this.editor.getSelection() + value);
- let to = this.editor.getPosition(this.editor.getOffset(from) + value.length);
- this.editor.setSelection(from, to);
- return;
- }
- let text = this.editor.getText();
- this.editor.setText(text + value);
- let [ from, to ] = this.editor.getPosition(text.length, (text + value).length);
- this.editor.setSelection(from, to);
- },
- /**
- * Write out an error at the current insertion point as a block comment
- * @param object aValue
- * The error object to write out the message and stack trace. It must
- * contain an |exception| property with the actual error thrown, but it
- * will often be the entire response of an evaluateJS request.
- * @return Promise
- * The promise that indicates when writing the comment completes.
- */
- writeAsErrorComment: function SP_writeAsErrorComment(aError)
- {
- let deferred = promise.defer();
- if (VariablesView.isPrimitive({ value: aError.exception })) {
- let error = aError.exception;
- let type = error.type;
- if (type == "undefined" ||
- type == "null" ||
- type == "Infinity" ||
- type == "-Infinity" ||
- type == "NaN" ||
- type == "-0") {
- deferred.resolve(type);
- }
- else if (type == "longString") {
- deferred.resolve(error.initial + "\u2026");
- }
- else {
- deferred.resolve(error);
- }
- } else if ("preview" in aError.exception) {
- let error = aError.exception;
- let stack = this._constructErrorStack(error.preview);
- if (typeof aError.exceptionMessage == "string") {
- deferred.resolve(aError.exceptionMessage + stack);
- } else {
- deferred.resolve(stack);
- }
- } else {
- // If there is no preview information, we need to ask the server for more.
- let objectClient = new ObjectClient(this.debuggerClient, aError.exception);
- objectClient.getPrototypeAndProperties(aResponse => {
- if (aResponse.error) {
- deferred.reject(aResponse);
- return;
- }
- let { ownProperties, safeGetterValues } = aResponse;
- let error = Object.create(null);
- // Combine all the property descriptor/getter values into one object.
- for (let key of Object.keys(safeGetterValues)) {
- error[key] = safeGetterValues[key].getterValue;
- }
- for (let key of Object.keys(ownProperties)) {
- error[key] = ownProperties[key].value;
- }
- let stack = this._constructErrorStack(error);
- if (typeof error.message == "string") {
- deferred.resolve(error.message + stack);
- }
- else {
- objectClient.getDisplayString(aResponse => {
- if (aResponse.error) {
- deferred.reject(aResponse);
- }
- else if (typeof aResponse.displayString == "string") {
- deferred.resolve(aResponse.displayString + stack);
- }
- else {
- deferred.resolve(stack);
- }
- });
- }
- });
- }
- return deferred.promise.then(aMessage => {
- console.error(aMessage);
- this.writeAsComment("Exception: " + aMessage);
- });
- },
- /**
- * Assembles the best possible stack from the properties of the provided
- * error.
- */
- _constructErrorStack(error) {
- let stack;
- if (typeof error.stack == "string" && error.stack) {
- stack = error.stack;
- } else if (typeof error.fileName == "string") {
- stack = "@" + error.fileName;
- if (typeof error.lineNumber == "number") {
- stack += ":" + error.lineNumber;
- }
- } else if (typeof error.filename == "string") {
- stack = "@" + error.filename;
- if (typeof error.lineNumber == "number") {
- stack += ":" + error.lineNumber;
- if (typeof error.columnNumber == "number") {
- stack += ":" + error.columnNumber;
- }
- }
- } else if (typeof error.lineNumber == "number") {
- stack = "@" + error.lineNumber;
- if (typeof error.columnNumber == "number") {
- stack += ":" + error.columnNumber;
- }
- }
- return stack ? "\n" + stack.replace(/\n$/, "") : "";
- },
- // Menu Operations
- /**
- * Open a new Scratchpad window.
- *
- * @return nsIWindow
- */
- openScratchpad: function SP_openScratchpad()
- {
- return ScratchpadManager.openScratchpad();
- },
- /**
- * Export the textbox content to a file.
- *
- * @param nsILocalFile aFile
- * The file where you want to save the textbox content.
- * @param boolean aNoConfirmation
- * If the file already exists, ask for confirmation?
- * @param boolean aSilentError
- * True if you do not want to display an error when file save fails,
- * false otherwise.
- * @param function aCallback
- * Optional function you want to call when file save completes. It will
- * get the following arguments:
- * 1) the nsresult status code for the export operation.
- */
- exportToFile: function SP_exportToFile(aFile, aNoConfirmation, aSilentError,
- aCallback)
- {
- if (!aNoConfirmation && aFile.exists() &&
- !window.confirm(this.strings.
- GetStringFromName("export.fileOverwriteConfirmation"))) {
- return;
- }
- let encoder = new TextEncoder();
- let buffer = encoder.encode(this.getText());
- let writePromise = OS.File.writeAtomic(aFile.path, buffer, {tmpPath: aFile.path + ".tmp"});
- writePromise.then(value => {
- if (aCallback) {
- aCallback.call(this, Components.results.NS_OK);
- }
- }, reason => {
- if (!aSilentError) {
- window.alert(this.strings.GetStringFromName("saveFile.failed"));
- }
- if (aCallback) {
- aCallback.call(this, Components.results.NS_ERROR_UNEXPECTED);
- }
- });
- },
- /**
- * Get a list of applicable charsets.
- * The best charset, defaulting to "UTF-8"
- *
- * @param string aBestCharset
- * @return array of strings
- */
- _getApplicableCharsets: function SP__getApplicableCharsets(aBestCharset = "UTF-8") {
- let charsets = Services.prefs.getCharPref(
- FALLBACK_CHARSET_LIST).split(",").filter(function (value) {
- return value.length;
- });
- charsets.unshift(aBestCharset);
- return charsets;
- },
- /**
- * Get content converted to unicode, using a list of input charset to try.
- *
- * @param string aContent
- * @param array of string aCharsetArray
- * @return string
- */
- _getUnicodeContent: function SP__getUnicodeContent(aContent, aCharsetArray) {
- let content = null,
- converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Ci.nsIScriptableUnicodeConverter),
- success = aCharsetArray.some(charset => {
- try {
- converter.charset = charset;
- content = converter.ConvertToUnicode(aContent);
- return true;
- } catch (e) {
- this.notificationBox.appendNotification(
- this.strings.formatStringFromName("importFromFile.convert.failed",
- [ charset ], 1),
- "file-import-convert-failed",
- null,
- this.notificationBox.PRIORITY_WARNING_HIGH,
- null);
- }
- });
- return content;
- },
- /**
- * Read the content of a file and put it into the textbox.
- *
- * @param nsILocalFile aFile
- * The file you want to save the textbox content into.
- * @param boolean aSilentError
- * True if you do not want to display an error when file load fails,
- * false otherwise.
- * @param function aCallback
- * Optional function you want to call when file load completes. It will
- * get the following arguments:
- * 1) the nsresult status code for the import operation.
- * 2) the data that was read from the file, if any.
- */
- importFromFile: function SP_importFromFile(aFile, aSilentError, aCallback)
- {
- // Prevent file type detection.
- let channel = NetUtil.newChannel({
- uri: NetUtil.newURI(aFile),
- loadingNode: window.document,
- securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
- contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER});
- channel.contentType = "application/javascript";
- this.notificationBox.removeAllNotifications(false);
- NetUtil.asyncFetch(channel, (aInputStream, aStatus) => {
- let content = null;
- if (Components.isSuccessCode(aStatus)) {
- let charsets = this._getApplicableCharsets();
- content = NetUtil.readInputStreamToString(aInputStream,
- aInputStream.available());
- content = this._getUnicodeContent(content, charsets);
- if (!content) {
- let message = this.strings.formatStringFromName(
- "importFromFile.convert.failed",
- [ charsets.join(", ") ],
- 1);
- this.notificationBox.appendNotification(
- message,
- "file-import-convert-failed",
- null,
- this.notificationBox.PRIORITY_CRITICAL_MEDIUM,
- null);
- if (aCallback) {
- aCallback.call(this, aStatus, content);
- }
- return;
- }
- // Check to see if the first line is a mode-line comment.
- let line = content.split("\n")[0];
- let modeline = this._scanModeLine(line);
- let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
- if (chrome && modeline["-sp-context"] === "browser") {
- this.setBrowserContext();
- }
- this.editor.setText(content);
- this.editor.clearHistory();
- this.dirty = false;
- document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
- }
- else if (!aSilentError) {
- window.alert(this.strings.GetStringFromName("openFile.failed"));
- }
- this.setFilename(aFile.path);
- this.setRecentFile(aFile);
- if (aCallback) {
- aCallback.call(this, aStatus, content);
- }
- });
- },
- /**
- * Open a file to edit in the Scratchpad.
- *
- * @param integer aIndex
- * Optional integer: clicked menuitem in the 'Open Recent'-menu.
- */
- openFile: function SP_openFile(aIndex)
- {
- let promptCallback = aFile => {
- this.promptSave((aCloseFile, aSaved, aStatus) => {
- let shouldOpen = aCloseFile;
- if (aSaved && !Components.isSuccessCode(aStatus)) {
- shouldOpen = false;
- }
- if (shouldOpen) {
- let file;
- if (aFile) {
- file = aFile;
- } else {
- file = Components.classes["@mozilla.org/file/local;1"].
- createInstance(Components.interfaces.nsILocalFile);
- let filePath = this.getRecentFiles()[aIndex];
- file.initWithPath(filePath);
- }
- if (!file.exists()) {
- this.notificationBox.appendNotification(
- this.strings.GetStringFromName("fileNoLongerExists.notification"),
- "file-no-longer-exists",
- null,
- this.notificationBox.PRIORITY_WARNING_HIGH,
- null);
- this.clearFiles(aIndex, 1);
- return;
- }
- this.importFromFile(file, false);
- }
- });
- };
- if (aIndex > -1) {
- promptCallback();
- } else {
- let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
- fp.init(window, this.strings.GetStringFromName("openFile.title"),
- Ci.nsIFilePicker.modeOpen);
- fp.defaultString = "";
- fp.appendFilter("JavaScript Files", "*.js; *.jsm; *.json");
- fp.appendFilter("All Files", "*.*");
- fp.open(aResult => {
- if (aResult != Ci.nsIFilePicker.returnCancel) {
- promptCallback(fp.file);
- }
- });
- }
- },
- /**
- * Get recent files.
- *
- * @return Array
- * File paths.
- */
- getRecentFiles: function SP_getRecentFiles()
- {
- let branch = Services.prefs.getBranch("devtools.scratchpad.");
- let filePaths = [];
- // WARNING: Do not use getCharPref here, it doesn't play nicely with
- // Unicode strings.
- if (branch.prefHasUserValue("recentFilePaths")) {
- let data = branch.getComplexValue("recentFilePaths",
- Ci.nsISupportsString).data;
- filePaths = JSON.parse(data);
- }
- return filePaths;
- },
- /**
- * Save a recent file in a JSON parsable string.
- *
- * @param nsILocalFile aFile
- * The nsILocalFile we want to save as a recent file.
- */
- setRecentFile: function SP_setRecentFile(aFile)
- {
- let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
- if (maxRecent < 1) {
- return;
- }
- let filePaths = this.getRecentFiles();
- let filesCount = filePaths.length;
- let pathIndex = filePaths.indexOf(aFile.path);
- // We are already storing this file in the list of recent files.
- if (pathIndex > -1) {
- // If it's already the most recent file, we don't have to do anything.
- if (pathIndex === (filesCount - 1)) {
- // Updating the menu to clear the disabled state from the wrong menuitem
- // in rare cases when two or more Scratchpad windows are open and the
- // same file has been opened in two or more windows.
- this.populateRecentFilesMenu();
- return;
- }
- // It is not the most recent file. Remove it from the list, we add it as
- // the most recent farther down.
- filePaths.splice(pathIndex, 1);
- }
- // If we are not storing the file and the 'recent files'-list is full,
- // remove the oldest file from the list.
- else if (filesCount === maxRecent) {
- filePaths.shift();
- }
- filePaths.push(aFile.path);
- // WARNING: Do not use setCharPref here, it doesn't play nicely with
- // Unicode strings.
- let str = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- str.data = JSON.stringify(filePaths);
- let branch = Services.prefs.getBranch("devtools.scratchpad.");
- branch.setComplexValue("recentFilePaths",
- Ci.nsISupportsString, str);
- },
- /**
- * Populates the 'Open Recent'-menu.
- */
- populateRecentFilesMenu: function SP_populateRecentFilesMenu()
- {
- let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
- let recentFilesMenu = document.getElementById("sp-open_recent-menu");
- if (maxRecent < 1) {
- recentFilesMenu.setAttribute("hidden", true);
- return;
- }
- let recentFilesPopup = recentFilesMenu.firstChild;
- let filePaths = this.getRecentFiles();
- let filename = this.getState().filename;
- recentFilesMenu.setAttribute("disabled", true);
- while (recentFilesPopup.hasChildNodes()) {
- recentFilesPopup.removeChild(recentFilesPopup.firstChild);
- }
- if (filePaths.length > 0) {
- recentFilesMenu.removeAttribute("disabled");
- // Print out menuitems with the most recent file first.
- for (let i = filePaths.length - 1; i >= 0; --i) {
- let menuitem = document.createElement("menuitem");
- menuitem.setAttribute("type", "radio");
- menuitem.setAttribute("label", filePaths[i]);
- if (filePaths[i] === filename) {
- menuitem.setAttribute("checked", true);
- menuitem.setAttribute("disabled", true);
- }
- menuitem.addEventListener("command", Scratchpad.openFile.bind(Scratchpad, i));
- recentFilesPopup.appendChild(menuitem);
- }
- recentFilesPopup.appendChild(document.createElement("menuseparator"));
- let clearItems = document.createElement("menuitem");
- clearItems.setAttribute("id", "sp-menu-clear_recent");
- clearItems.setAttribute("label",
- this.strings.
- GetStringFromName("clearRecentMenuItems.label"));
- clearItems.setAttribute("command", "sp-cmd-clearRecentFiles");
- recentFilesPopup.appendChild(clearItems);
- }
- },
- /**
- * Clear a range of files from the list.
- *
- * @param integer aIndex
- * Index of file in menu to remove.
- * @param integer aLength
- * Number of files from the index 'aIndex' to remove.
- */
- clearFiles: function SP_clearFile(aIndex, aLength)
- {
- let filePaths = this.getRecentFiles();
- filePaths.splice(aIndex, aLength);
- // WARNING: Do not use setCharPref here, it doesn't play nicely with
- // Unicode strings.
- let str = Cc["@mozilla.org/supports-string;1"]
- .createInstance(Ci.nsISupportsString);
- str.data = JSON.stringify(filePaths);
- let branch = Services.prefs.getBranch("devtools.scratchpad.");
- branch.setComplexValue("recentFilePaths",
- Ci.nsISupportsString, str);
- },
- /**
- * Clear all recent files.
- */
- clearRecentFiles: function SP_clearRecentFiles()
- {
- Services.prefs.clearUserPref("devtools.scratchpad.recentFilePaths");
- },
- /**
- * Handle changes to the 'PREF_RECENT_FILES_MAX'-preference.
- */
- handleRecentFileMaxChange: function SP_handleRecentFileMaxChange()
- {
- let maxRecent = Services.prefs.getIntPref(PREF_RECENT_FILES_MAX);
- let menu = document.getElementById("sp-open_recent-menu");
- // Hide the menu if the 'PREF_RECENT_FILES_MAX'-pref is set to zero or less.
- if (maxRecent < 1) {
- menu.setAttribute("hidden", true);
- } else {
- if (menu.hasAttribute("hidden")) {
- if (!menu.firstChild.hasChildNodes()) {
- this.populateRecentFilesMenu();
- }
- menu.removeAttribute("hidden");
- }
- let filePaths = this.getRecentFiles();
- if (maxRecent < filePaths.length) {
- let diff = filePaths.length - maxRecent;
- this.clearFiles(0, diff);
- }
- }
- },
- /**
- * Save the textbox content to the currently open file.
- *
- * @param function aCallback
- * Optional function you want to call when file is saved
- */
- saveFile: function SP_saveFile(aCallback)
- {
- if (!this.filename) {
- return this.saveFileAs(aCallback);
- }
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
- file.initWithPath(this.filename);
- this.exportToFile(file, true, false, aStatus => {
- if (Components.isSuccessCode(aStatus)) {
- this.dirty = false;
- document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
- this.setRecentFile(file);
- }
- if (aCallback) {
- aCallback(aStatus);
- }
- });
- },
- /**
- * Save the textbox content to a new file.
- *
- * @param function aCallback
- * Optional function you want to call when file is saved
- */
- saveFileAs: function SP_saveFileAs(aCallback)
- {
- let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
- let fpCallback = aResult => {
- if (aResult != Ci.nsIFilePicker.returnCancel) {
- this.setFilename(fp.file.path);
- this.exportToFile(fp.file, true, false, aStatus => {
- if (Components.isSuccessCode(aStatus)) {
- this.dirty = false;
- this.setRecentFile(fp.file);
- }
- if (aCallback) {
- aCallback(aStatus);
- }
- });
- }
- };
- fp.init(window, this.strings.GetStringFromName("saveFileAs"),
- Ci.nsIFilePicker.modeSave);
- fp.defaultString = "scratchpad.js";
- fp.appendFilter("JavaScript Files", "*.js; *.jsm; *.json");
- fp.appendFilter("All Files", "*.*");
- fp.open(fpCallback);
- },
- /**
- * Restore content from saved version of current file.
- *
- * @param function aCallback
- * Optional function you want to call when file is saved
- */
- revertFile: function SP_revertFile(aCallback)
- {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
- file.initWithPath(this.filename);
- if (!file.exists()) {
- return;
- }
- this.importFromFile(file, false, (aStatus, aContent) => {
- if (aCallback) {
- aCallback(aStatus);
- }
- });
- },
- /**
- * Prompt to revert scratchpad if it has unsaved changes.
- *
- * @param function aCallback
- * Optional function you want to call when file is saved. The callback
- * receives three arguments:
- * - aRevert (boolean) - tells if the file has been reverted.
- * - status (number) - the file revert status result (if the file was
- * saved).
- */
- promptRevert: function SP_promptRervert(aCallback)
- {
- if (this.filename) {
- let ps = Services.prompt;
- let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_REVERT +
- ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL;
- let button = ps.confirmEx(window,
- this.strings.GetStringFromName("confirmRevert.title"),
- this.strings.GetStringFromName("confirmRevert"),
- flags, null, null, null, null, {});
- if (button == BUTTON_POSITION_CANCEL) {
- if (aCallback) {
- aCallback(false);
- }
- return;
- }
- if (button == BUTTON_POSITION_REVERT) {
- this.revertFile(aStatus => {
- if (aCallback) {
- aCallback(true, aStatus);
- }
- });
- return;
- }
- }
- if (aCallback) {
- aCallback(false);
- }
- },
- /**
- * Open the Error Console.
- */
- openErrorConsole: function SP_openErrorConsole()
- {
- HUDService.toggleBrowserConsole();
- },
- /**
- * Open the Web Console.
- */
- openWebConsole: function SP_openWebConsole()
- {
- let target = TargetFactory.forTab(this.gBrowser.selectedTab);
- gDevTools.showToolbox(target, "webconsole");
- this.browserWindow.focus();
- },
- /**
- * Set the current execution context to be the active tab content window.
- */
- setContentContext: function SP_setContentContext()
- {
- if (this.executionContext == SCRATCHPAD_CONTEXT_CONTENT) {
- return;
- }
- let content = document.getElementById("sp-menu-content");
- document.getElementById("sp-menu-browser").removeAttribute("checked");
- document.getElementById("sp-cmd-reloadAndRun").removeAttribute("disabled");
- content.setAttribute("checked", true);
- this.executionContext = SCRATCHPAD_CONTEXT_CONTENT;
- this.notificationBox.removeAllNotifications(false);
- },
- /**
- * Set the current execution context to be the most recent chrome window.
- */
- setBrowserContext: function SP_setBrowserContext()
- {
- if (this.executionContext == SCRATCHPAD_CONTEXT_BROWSER) {
- return;
- }
- let browser = document.getElementById("sp-menu-browser");
- let reloadAndRun = document.getElementById("sp-cmd-reloadAndRun");
- document.getElementById("sp-menu-content").removeAttribute("checked");
- reloadAndRun.setAttribute("disabled", true);
- browser.setAttribute("checked", true);
- this.executionContext = SCRATCHPAD_CONTEXT_BROWSER;
- this.notificationBox.appendNotification(
- this.strings.GetStringFromName("browserContext.notification"),
- SCRATCHPAD_CONTEXT_BROWSER,
- null,
- this.notificationBox.PRIORITY_WARNING_HIGH,
- null);
- },
- /**
- * Gets the ID of the inner window of the given DOM window object.
- *
- * @param nsIDOMWindow aWindow
- * @return integer
- * the inner window ID
- */
- getInnerWindowId: function SP_getInnerWindowId(aWindow)
- {
- return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
- getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
- },
- updateStatusBar: function SP_updateStatusBar(aEventType)
- {
- var statusBarField = document.getElementById("statusbar-line-col");
- let { line, ch } = this.editor.getCursor();
- statusBarField.textContent = this.strings.formatStringFromName(
- "scratchpad.statusBarLineCol", [ line + 1, ch + 1], 2);
- },
- /**
- * The Scratchpad window load event handler. This method
- * initializes the Scratchpad window and source editor.
- *
- * @param nsIDOMEvent aEvent
- */
- onLoad: function SP_onLoad(aEvent)
- {
- if (aEvent.target != document) {
- return;
- }
- let chrome = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
- if (chrome) {
- let environmentMenu = document.getElementById("sp-environment-menu");
- let errorConsoleCommand = document.getElementById("sp-cmd-errorConsole");
- let chromeContextCommand = document.getElementById("sp-cmd-browserContext");
- environmentMenu.removeAttribute("hidden");
- chromeContextCommand.removeAttribute("disabled");
- errorConsoleCommand.removeAttribute("disabled");
- }
- let initialText = this.strings.formatStringFromName(
- "scratchpadIntro1",
- [ShortcutUtils.prettifyShortcut(document.getElementById("sp-key-run"), true),
- ShortcutUtils.prettifyShortcut(document.getElementById("sp-key-inspect"), true),
- ShortcutUtils.prettifyShortcut(document.getElementById("sp-key-display"), true)],
- 3);
- let args = window.arguments;
- let state = null;
- if (args && args[0] instanceof Ci.nsIDialogParamBlock) {
- args = args[0];
- this._instanceId = args.GetString(0);
- state = args.GetString(1) || null;
- if (state) {
- state = JSON.parse(state);
- this.setState(state);
- if ("text" in state) {
- initialText = state.text;
- }
- }
- } else {
- this._instanceId = ScratchpadManager.createUid();
- }
- let config = {
- mode: Editor.modes.js,
- value: initialText,
- lineNumbers: Services.prefs.getBoolPref(SHOW_LINE_NUMBERS),
- contextMenu: "scratchpad-text-popup",
- showTrailingSpace: Services.prefs.getBoolPref(SHOW_TRAILING_SPACE),
- autocomplete: Services.prefs.getBoolPref(ENABLE_AUTOCOMPLETION),
- lineWrapping: Services.prefs.getBoolPref(WRAP_TEXT),
- };
- this.editor = new Editor(config);
- let editorElement = document.querySelector("#scratchpad-editor");
- this.editor.appendTo(editorElement).then(() => {
- var lines = initialText.split("\n");
- this.editor.setFontSize(Services.prefs.getIntPref(EDITOR_FONT_SIZE));
- this.editor.on("change", this._onChanged);
- // Keep a reference to the bound version for use in onUnload.
- this.updateStatusBar = Scratchpad.updateStatusBar.bind(this);
- this.editor.on("cursorActivity", this.updateStatusBar);
- let okstring = this.strings.GetStringFromName("selfxss.okstring");
- let msg = this.strings.formatStringFromName("selfxss.msg", [okstring], 1);
- this._onPaste = WebConsoleUtils.pasteHandlerGen(this.editor.container.contentDocument.body,
- document.querySelector("#scratchpad-notificationbox"),
- msg, okstring);
- editorElement.addEventListener("paste", this._onPaste, true);
- editorElement.addEventListener("drop", this._onPaste);
- this.editor.on("saveRequested", () => this.saveFile());
- this.editor.focus();
- this.editor.setCursor({ line: lines.length, ch: lines.pop().length });
- // Add the commands controller for the source-editor.
- this.editor.insertCommandsController();
- if (state)
- this.dirty = !state.saved;
- this.initialized = true;
- this._triggerObservers("Ready");
- this.populateRecentFilesMenu();
- PreferenceObserver.init();
- CloseObserver.init();
- }).then(null, (err) => console.error(err));
- this._setupCommandListeners();
- this._updateViewMenuItems();
- this._setupPopupShowingListeners();
- },
- /**
- * The Source Editor "change" event handler. This function updates the
- * Scratchpad window title to show an asterisk when there are unsaved changes.
- *
- * @private
- */
- _onChanged: function SP__onChanged()
- {
- Scratchpad._updateTitle();
- if (Scratchpad.filename) {
- if (Scratchpad.dirty)
- document.getElementById("sp-cmd-revert").removeAttribute("disabled");
- else
- document.getElementById("sp-cmd-revert").setAttribute("disabled", true);
- }
- },
- /**
- * Undo the last action of the user.
- */
- undo: function SP_undo()
- {
- this.editor.undo();
- },
- /**
- * Redo the previously undone action.
- */
- redo: function SP_redo()
- {
- this.editor.redo();
- },
- /**
- * The Scratchpad window unload event handler. This method unloads/destroys
- * the source editor.
- *
- * @param nsIDOMEvent aEvent
- */
- onUnload: function SP_onUnload(aEvent)
- {
- if (aEvent.target != document) {
- return;
- }
- // This event is created only after user uses 'reload and run' feature.
- if (this._reloadAndRunEvent && this.gBrowser) {
- this.gBrowser.selectedBrowser.removeEventListener("load",
- this._reloadAndRunEvent, true);
- }
- PreferenceObserver.uninit();
- CloseObserver.uninit();
- if (this._onPaste) {
- let editorElement = document.querySelector("#scratchpad-editor");
- editorElement.removeEventListener("paste", this._onPaste, true);
- editorElement.removeEventListener("drop", this._onPaste);
- this._onPaste = null;
- }
- this.editor.off("change", this._onChanged);
- this.editor.off("cursorActivity", this.updateStatusBar);
- this.editor.destroy();
- this.editor = null;
- if (this._sidebar) {
- this._sidebar.destroy();
- this._sidebar = null;
- }
- if (this._prettyPrintWorker) {
- this._prettyPrintWorker.destroy();
- this._prettyPrintWorker = null;
- }
- scratchpadTargets = null;
- this.webConsoleClient = null;
- this.debuggerClient = null;
- this.initialized = false;
- },
- /**
- * Prompt to save scratchpad if it has unsaved changes.
- *
- * @param function aCallback
- * Optional function you want to call when file is saved. The callback
- * receives three arguments:
- * - toClose (boolean) - tells if the window should be closed.
- * - saved (boolen) - tells if the file has been saved.
- * - status (number) - the file save status result (if the file was
- * saved).
- * @return boolean
- * Whether the window should be closed
- */
- promptSave: function SP_promptSave(aCallback)
- {
- if (this.dirty) {
- let ps = Services.prompt;
- let flags = ps.BUTTON_POS_0 * ps.BUTTON_TITLE_SAVE +
- ps.BUTTON_POS_1 * ps.BUTTON_TITLE_CANCEL +
- ps.BUTTON_POS_2 * ps.BUTTON_TITLE_DONT_SAVE;
- let button = ps.confirmEx(window,
- this.strings.GetStringFromName("confirmClose.title"),
- this.strings.GetStringFromName("confirmClose"),
- flags, null, null, null, null, {});
- if (button == BUTTON_POSITION_CANCEL) {
- if (aCallback) {
- aCallback(false, false);
- }
- return false;
- }
- if (button == BUTTON_POSITION_SAVE) {
- this.saveFile(aStatus => {
- if (aCallback) {
- aCallback(true, true, aStatus);
- }
- });
- return true;
- }
- }
- if (aCallback) {
- aCallback(true, false);
- }
- return true;
- },
- /**
- * Handler for window close event. Prompts to save scratchpad if
- * there are unsaved changes.
- *
- * @param nsIDOMEvent aEvent
- * @param function aCallback
- * Optional function you want to call when file is saved/closed.
- * Used mainly for tests.
- */
- onClose: function SP_onClose(aEvent, aCallback)
- {
- aEvent.preventDefault();
- this.close(aCallback);
- },
- /**
- * Close the scratchpad window. Prompts before closing if the scratchpad
- * has unsaved changes.
- *
- * @param function aCallback
- * Optional function you want to call when file is saved
- */
- close: function SP_close(aCallback)
- {
- let shouldClose;
- this.promptSave((aShouldClose, aSaved, aStatus) => {
- shouldClose = aShouldClose;
- if (aSaved && !Components.isSuccessCode(aStatus)) {
- shouldClose = false;
- }
- if (shouldClose) {
- window.close();
- }
- if (aCallback) {
- aCallback(shouldClose);
- }
- });
- return shouldClose;
- },
- /**
- * Toggle a editor's boolean option.
- */
- toggleEditorOption: function SP_toggleEditorOption(optionName, optionPreference)
- {
- let newOptionValue = !this.editor.getOption(optionName);
- this.editor.setOption(optionName, newOptionValue);
- Services.prefs.setBoolPref(optionPreference, newOptionValue);
- },
- /**
- * Increase the editor's font size by 1 px.
- */
- increaseFontSize: function SP_increaseFontSize()
- {
- let size = this.editor.getFontSize();
- if (size < MAXIMUM_FONT_SIZE) {
- let newFontSize = size + 1;
- this.editor.setFontSize(newFontSize);
- Services.prefs.setIntPref(EDITOR_FONT_SIZE, newFontSize);
- if (newFontSize === MAXIMUM_FONT_SIZE) {
- document.getElementById("sp-cmd-larger-font").setAttribute("disabled", true);
- }
- document.getElementById("sp-cmd-smaller-font").removeAttribute("disabled");
- }
- },
- /**
- * Decrease the editor's font size by 1 px.
- */
- decreaseFontSize: function SP_decreaseFontSize()
- {
- let size = this.editor.getFontSize();
- if (size > MINIMUM_FONT_SIZE) {
- let newFontSize = size - 1;
- this.editor.setFontSize(newFontSize);
- Services.prefs.setIntPref(EDITOR_FONT_SIZE, newFontSize);
- if (newFontSize === MINIMUM_FONT_SIZE) {
- document.getElementById("sp-cmd-smaller-font").setAttribute("disabled", true);
- }
- }
- document.getElementById("sp-cmd-larger-font").removeAttribute("disabled");
- },
- /**
- * Restore the editor's original font size.
- */
- normalFontSize: function SP_normalFontSize()
- {
- this.editor.setFontSize(NORMAL_FONT_SIZE);
- Services.prefs.setIntPref(EDITOR_FONT_SIZE, NORMAL_FONT_SIZE);
- document.getElementById("sp-cmd-larger-font").removeAttribute("disabled");
- document.getElementById("sp-cmd-smaller-font").removeAttribute("disabled");
- },
- _observers: [],
- /**
- * Add an observer for Scratchpad events.
- *
- * The observer implements IScratchpadObserver := {
- * onReady: Called when the Scratchpad and its Editor are ready.
- * Arguments: (Scratchpad aScratchpad)
- * }
- *
- * All observer handlers are optional.
- *
- * @param IScratchpadObserver aObserver
- * @see removeObserver
- */
- addObserver: function SP_addObserver(aObserver)
- {
- this._observers.push(aObserver);
- },
- /**
- * Remove an observer for Scratchpad events.
- *
- * @param IScratchpadObserver aObserver
- * @see addObserver
- */
- removeObserver: function SP_removeObserver(aObserver)
- {
- let index = this._observers.indexOf(aObserver);
- if (index != -1) {
- this._observers.splice(index, 1);
- }
- },
- /**
- * Trigger named handlers in Scratchpad observers.
- *
- * @param string aName
- * Name of the handler to trigger.
- * @param Array aArgs
- * Optional array of arguments to pass to the observer(s).
- * @see addObserver
- */
- _triggerObservers: function SP_triggerObservers(aName, aArgs)
- {
- // insert this Scratchpad instance as the first argument
- if (!aArgs) {
- aArgs = [this];
- } else {
- aArgs.unshift(this);
- }
- // trigger all observers that implement this named handler
- for (let i = 0; i < this._observers.length; ++i) {
- let observer = this._observers[i];
- let handler = observer["on" + aName];
- if (handler) {
- handler.apply(observer, aArgs);
- }
- }
- },
- /**
- * Opens the MDN documentation page for Scratchpad.
- */
- openDocumentationPage: function SP_openDocumentationPage()
- {
- let url = this.strings.GetStringFromName("help.openDocumentationPage");
- this.browserWindow.openUILinkIn(url,"tab");
- this.browserWindow.focus();
- },
- };
- /**
- * Represents the DebuggerClient connection to a specific tab as used by the
- * Scratchpad.
- *
- * @param object aTab
- * The tab to connect to.
- */
- function ScratchpadTab(aTab)
- {
- this._tab = aTab;
- }
- var scratchpadTargets = new WeakMap();
- /**
- * Returns the object containing the DebuggerClient and WebConsoleClient for a
- * given tab or window.
- *
- * @param object aSubject
- * The tab or window to obtain the connection for.
- * @return Promise
- * The promise for the connection information.
- */
- ScratchpadTab.consoleFor = function consoleFor(aSubject)
- {
- if (!scratchpadTargets.has(aSubject)) {
- scratchpadTargets.set(aSubject, new this(aSubject));
- }
- return scratchpadTargets.get(aSubject).connect(aSubject);
- };
- ScratchpadTab.prototype = {
- /**
- * The promise for the connection.
- */
- _connector: null,
- /**
- * Initialize a debugger client and connect it to the debugger server.
- *
- * @param object aSubject
- * The tab or window to obtain the connection for.
- * @return Promise
- * The promise for the result of connecting to this tab or window.
- */
- connect: function ST_connect(aSubject)
- {
- if (this._connector) {
- return this._connector;
- }
- let deferred = promise.defer();
- this._connector = deferred.promise;
- let connectTimer = setTimeout(() => {
- deferred.reject({
- error: "timeout",
- message: Scratchpad.strings.GetStringFromName("connectionTimeout"),
- });
- }, REMOTE_TIMEOUT);
- deferred.promise.then(() => clearTimeout(connectTimer));
- this._attach(aSubject).then(aTarget => {
- let consoleActor = aTarget.form.consoleActor;
- let client = aTarget.client;
- client.attachConsole(consoleActor, [], (aResponse, aWebConsoleClient) => {
- if (aResponse.error) {
- reportError("attachConsole", aResponse);
- deferred.reject(aResponse);
- }
- else {
- deferred.resolve({
- webConsoleClient: aWebConsoleClient,
- debuggerClient: client
- });
- }
- });
- });
- return deferred.promise;
- },
- /**
- * Attach to this tab.
- *
- * @param object aSubject
- * The tab or window to obtain the connection for.
- * @return Promise
- * The promise for the TabTarget for this tab.
- */
- _attach: function ST__attach(aSubject)
- {
- let target = TargetFactory.forTab(this._tab);
- target.once("close", () => {
- if (scratchpadTargets) {
- scratchpadTargets.delete(aSubject);
- }
- });
- return target.makeRemote().then(() => target);
- },
- };
- /**
- * Represents the DebuggerClient connection to a specific window as used by the
- * Scratchpad.
- */
- function ScratchpadWindow() {}
- ScratchpadWindow.consoleFor = ScratchpadTab.consoleFor;
- ScratchpadWindow.prototype = Heritage.extend(ScratchpadTab.prototype, {
- /**
- * Attach to this window.
- *
- * @return Promise
- * The promise for the target for this window.
- */
- _attach: function SW__attach()
- {
- if (!DebuggerServer.initialized) {
- DebuggerServer.init();
- DebuggerServer.addBrowserActors();
- }
- DebuggerServer.allowChromeProcess = true;
- let client = new DebuggerClient(DebuggerServer.connectPipe());
- return client.connect()
- .then(() => client.getProcess())
- .then(aResponse => {
- return { form: aResponse.form, client: client };
- });
- }
- });
- function ScratchpadTarget(aTarget)
- {
- this._target = aTarget;
- }
- ScratchpadTarget.consoleFor = ScratchpadTab.consoleFor;
- ScratchpadTarget.prototype = Heritage.extend(ScratchpadTab.prototype, {
- _attach: function ST__attach()
- {
- if (this._target.isRemote) {
- return promise.resolve(this._target);
- }
- return this._target.makeRemote().then(() => this._target);
- }
- });
- /**
- * Encapsulates management of the sidebar containing the VariablesView for
- * object inspection.
- */
- function ScratchpadSidebar(aScratchpad)
- {
- // Make sure to decorate this object. ToolSidebar requires the parent
- // panel to support event (emit) API.
- EventEmitter.decorate(this);
- let ToolSidebar = require("devtools/client/framework/sidebar").ToolSidebar;
- let tabbox = document.querySelector("#scratchpad-sidebar");
- this._sidebar = new ToolSidebar(tabbox, this, "scratchpad");
- this._scratchpad = aScratchpad;
- }
- ScratchpadSidebar.prototype = {
- /*
- * The ToolSidebar for this sidebar.
- */
- _sidebar: null,
- /*
- * The VariablesView for this sidebar.
- */
- variablesView: null,
- /*
- * Whether the sidebar is currently shown.
- */
- visible: false,
- /**
- * Open the sidebar, if not open already, and populate it with the properties
- * of the given object.
- *
- * @param string aString
- * The string that was evaluated.
- * @param object aObject
- * The object to inspect, which is the aEvalString evaluation result.
- * @return Promise
- * A promise that will resolve once the sidebar is open.
- */
- open: function SS_open(aEvalString, aObject)
- {
- this.show();
- let deferred = promise.defer();
- let onTabReady = () => {
- if (this.variablesView) {
- this.variablesView.controller.releaseActors();
- }
- else {
- let window = this._sidebar.getWindowForTab("variablesview");
- let container = window.document.querySelector("#variables");
- this.variablesView = new VariablesView(container, {
- searchEnabled: true,
- searchPlaceholder: this._scratchpad.strings
- .GetStringFromName("propertiesFilterPlaceholder")
- });
- VariablesViewController.attach(this.variablesView, {
- getEnvironmentClient: aGrip => {
- return new EnvironmentClient(this._scratchpad.debuggerClient, aGrip);
- },
- getObjectClient: aGrip => {
- return new ObjectClient(this._scratchpad.debuggerClient, aGrip);
- },
- getLongStringClient: aActor => {
- return this._scratchpad.webConsoleClient.longString(aActor);
- },
- releaseActor: aActor => {
- this._scratchpad.debuggerClient.release(aActor);
- }
- });
- }
- this._update(aObject).then(() => deferred.resolve());
- };
- if (this._sidebar.getCurrentTabID() == "variablesview") {
- onTabReady();
- }
- else {
- this._sidebar.once("variablesview-ready", onTabReady);
- this._sidebar.addTab("variablesview", VARIABLES_VIEW_URL, {selected: true});
- }
- return deferred.promise;
- },
- /**
- * Show the sidebar.
- */
- show: function SS_show()
- {
- if (!this.visible) {
- this.visible = true;
- this._sidebar.show();
- }
- },
- /**
- * Hide the sidebar.
- */
- hide: function SS_hide()
- {
- if (this.visible) {
- this.visible = false;
- this._sidebar.hide();
- }
- },
- /**
- * Destroy the sidebar.
- *
- * @return Promise
- * The promise that resolves when the sidebar is destroyed.
- */
- destroy: function SS_destroy()
- {
- if (this.variablesView) {
- this.variablesView.controller.releaseActors();
- this.variablesView = null;
- }
- return this._sidebar.destroy();
- },
- /**
- * Update the object currently inspected by the sidebar.
- *
- * @param any aValue
- * The JS value to inspect in the sidebar.
- * @return Promise
- * A promise that resolves when the update completes.
- */
- _update: function SS__update(aValue)
- {
- let options, onlyEnumVisible;
- if (VariablesView.isPrimitive({ value: aValue })) {
- options = { rawObject: { value: aValue } };
- onlyEnumVisible = true;
- } else {
- options = { objectActor: aValue };
- onlyEnumVisible = false;
- }
- let view = this.variablesView;
- view.onlyEnumVisible = onlyEnumVisible;
- view.empty();
- return view.controller.setSingleVariable(options).expanded;
- }
- };
- /**
- * Report an error coming over the remote debugger protocol.
- *
- * @param string aAction
- * The name of the action or method that failed.
- * @param object aResponse
- * The response packet that contains the error.
- */
- function reportError(aAction, aResponse)
- {
- console.error(aAction + " failed: " + aResponse.error + " " +
- aResponse.message);
- }
- /**
- * The PreferenceObserver listens for preference changes while Scratchpad is
- * running.
- */
- var PreferenceObserver = {
- _initialized: false,
- init: function PO_init()
- {
- if (this._initialized) {
- return;
- }
- this.branch = Services.prefs.getBranch("devtools.scratchpad.");
- this.branch.addObserver("", this, false);
- this._initialized = true;
- },
- observe: function PO_observe(aMessage, aTopic, aData)
- {
- if (aTopic != "nsPref:changed") {
- return;
- }
- if (aData == "recentFilesMax") {
- Scratchpad.handleRecentFileMaxChange();
- }
- else if (aData == "recentFilePaths") {
- Scratchpad.populateRecentFilesMenu();
- }
- },
- uninit: function PO_uninit() {
- if (!this.branch) {
- return;
- }
- this.branch.removeObserver("", this);
- this.branch = null;
- }
- };
- /**
- * The CloseObserver listens for the last browser window closing and attempts to
- * close the Scratchpad.
- */
- var CloseObserver = {
- init: function CO_init()
- {
- Services.obs.addObserver(this, "browser-lastwindow-close-requested", false);
- },
- observe: function CO_observe(aSubject)
- {
- if (Scratchpad.close()) {
- this.uninit();
- }
- else {
- aSubject.QueryInterface(Ci.nsISupportsPRBool);
- aSubject.data = true;
- }
- },
- uninit: function CO_uninit()
- {
- // Will throw exception if removeObserver is called twice.
- if (this._uninited) {
- return;
- }
- this._uninited = true;
- Services.obs.removeObserver(this, "browser-lastwindow-close-requested",
- false);
- },
- };
- XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
- return Services.strings.createBundle(SCRATCHPAD_L10N);
- });
- addEventListener("load", Scratchpad.onLoad.bind(Scratchpad), false);
- addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false);
- addEventListener("close", Scratchpad.onClose.bind(Scratchpad), false);
|