/* 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/. */ var Cu = Components.utils; const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {}); const {TargetFactory} = require("devtools/client/framework/target"); const {console} = Cu.import("resource://gre/modules/Console.jsm", {}); const promise = require("promise"); const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {}); const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {}); const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor"); const DevToolsUtils = require("devtools/shared/DevToolsUtils"); const flags = require("devtools/shared/flags"); const TEST_URL_ROOT = "http://mochi.test:8888/browser/devtools/client/projecteditor/test/"; const SAMPLE_WEBAPP_URL = TEST_URL_ROOT + "/helper_homepage.html"; var TEMP_PATH; var TEMP_FOLDER_NAME = "ProjectEditor" + (new Date().getTime()); // All test are asynchronous waitForExplicitFinish(); // Uncomment this pref to dump all devtools emitted events to the console. // Services.prefs.setBoolPref("devtools.dump.emit", true); // Set the testing flag and reset it when the test ends flags.testing = true; registerCleanupFunction(() => flags.testing = false); // Clear preferences that may be set during the course of tests. registerCleanupFunction(() => { // Services.prefs.clearUserPref("devtools.dump.emit"); TEMP_PATH = null; TEMP_FOLDER_NAME = null; }); // Auto close the toolbox and close the test tabs when the test ends registerCleanupFunction(() => { try { let target = TargetFactory.forTab(gBrowser.selectedTab); gDevTools.closeToolbox(target); } catch (ex) { dump(ex); } while (gBrowser.tabs.length > 1) { gBrowser.removeCurrentTab(); } }); /** * Add a new test tab in the browser and load the given url. * @param {String} url The url to be loaded in the new tab * @return a promise that resolves to the tab object when the url is loaded */ function addTab(url) { info("Adding a new tab with URL: '" + url + "'"); let def = promise.defer(); let tab = gBrowser.selectedTab = gBrowser.addTab(url); BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(function () { info("URL '" + url + "' loading complete"); waitForFocus(() => { def.resolve(tab); }, content); }); return def.promise; } /** * Some tests may need to import one or more of the test helper scripts. * A test helper script is simply a js file that contains common test code that * is either not common-enough to be in head.js, or that is located in a separate * directory. * The script will be loaded synchronously and in the test's scope. * @param {String} filePath The file path, relative to the current directory. * Examples: * - "helper_attributes_test_runner.js" * - "../../../commandline/test/helpers.js" */ function loadHelperScript(filePath) { let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); Services.scriptloader.loadSubScript(testDir + "/" + filePath, this); } function addProjectEditorTabForTempDirectory(opts = {}) { try { TEMP_PATH = buildTempDirectoryStructure(); } catch (e) { // Bug 1037292 - The test servers sometimes are unable to // write to the temporary directory due to locked files // or access denied errors. Try again if this failed. info("Project Editor temp directory creation failed. Trying again."); TEMP_PATH = buildTempDirectoryStructure(); } let customOpts = { name: "Test", iconUrl: "chrome://devtools/skin/images/tool-options.svg", projectOverviewURL: SAMPLE_WEBAPP_URL }; info("Adding a project editor tab for editing at: " + TEMP_PATH); return addProjectEditorTab(opts).then((projecteditor) => { return projecteditor.setProjectToAppPath(TEMP_PATH, customOpts).then(() => { return projecteditor; }); }); } function addProjectEditorTab(opts = {}) { return addTab("chrome://devtools/content/projecteditor/chrome/content/projecteditor-test.xul").then(() => { let iframe = content.document.getElementById("projecteditor-iframe"); if (opts.menubar !== false) { opts.menubar = content.document.querySelector("menubar"); } let projecteditor = ProjectEditor.ProjectEditor(iframe, opts); ok(iframe, "Tab has placeholder iframe for projecteditor"); ok(projecteditor, "ProjectEditor has been initialized"); return projecteditor.loaded.then((projecteditor) => { return projecteditor; }); }); } /** * Build a temporary directory as a workspace for this loader * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O */ function buildTempDirectoryStructure() { let dirName = TEMP_FOLDER_NAME; info("Building a temporary directory at " + dirName); // First create (and remove) the temp dir to discard any changes let TEMP_DIR = FileUtils.getDir("TmpD", [dirName], true); TEMP_DIR.remove(true); // Now rebuild our fake project. TEMP_DIR = FileUtils.getDir("TmpD", [dirName], true); FileUtils.getDir("TmpD", [dirName, "css"], true); FileUtils.getDir("TmpD", [dirName, "data"], true); FileUtils.getDir("TmpD", [dirName, "img", "icons"], true); FileUtils.getDir("TmpD", [dirName, "js"], true); let htmlFile = FileUtils.getFile("TmpD", [dirName, "index.html"]); htmlFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); writeToFileSync(htmlFile, [ "", '', " ", ' ', " ProjectEditor Temp File", ' ', " ", ' ', "

ProjectEditor Temp File

", " ", ""].join("\n") ); let readmeFile = FileUtils.getFile("TmpD", [dirName, "README.md"]); readmeFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); writeToFileSync(readmeFile, [ "## Readme" ].join("\n") ); let licenseFile = FileUtils.getFile("TmpD", [dirName, "LICENSE"]); licenseFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); writeToFileSync(licenseFile, [ "/* 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/. */" ].join("\n") ); let cssFile = FileUtils.getFile("TmpD", [dirName, "css", "styles.css"]); cssFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); writeToFileSync(cssFile, [ "body {", " background: red;", "}" ].join("\n") ); FileUtils.getFile("TmpD", [dirName, "js", "script.js"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); FileUtils.getFile("TmpD", [dirName, "img", "fake.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); FileUtils.getFile("TmpD", [dirName, "img", "icons", "16x16.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); FileUtils.getFile("TmpD", [dirName, "img", "icons", "32x32.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); FileUtils.getFile("TmpD", [dirName, "img", "icons", "128x128.png"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); FileUtils.getFile("TmpD", [dirName, "img", "icons", "vector.svg"]).createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE); return TEMP_DIR.path; } // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file function writeToFile(file, data) { if (typeof file === "string") { file = new FileUtils.File(file); } info("Writing to file: " + file.path + " (exists? " + file.exists() + ")"); let defer = promise.defer(); var ostream = FileUtils.openSafeFileOutputStream(file); var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]. createInstance(Components.interfaces.nsIScriptableUnicodeConverter); converter.charset = "UTF-8"; var istream = converter.convertToInputStream(data); // The last argument (the callback) is optional. NetUtil.asyncCopy(istream, ostream, function (status) { if (!Components.isSuccessCode(status)) { // Handle error! info("ERROR WRITING TEMP FILE", status); } defer.resolve(); }); return defer.promise; } // This is used when setting up the test. // You should typically use the async version of this, writeToFile. // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#More function writeToFileSync(file, data) { // file is nsIFile, data is a string var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]. createInstance(Components.interfaces.nsIFileOutputStream); // use 0x02 | 0x10 to open file for appending. foStream.init(file, 0x02 | 0x08 | 0x20, 0o666, 0); // write, create, truncate // In a c file operation, we have no need to set file mode with or operation, // directly using "r" or "w" usually. // if you are sure there will never ever be any non-ascii text in data you can // also call foStream.write(data, data.length) directly var converter = Components.classes["@mozilla.org/intl/converter-output-stream;1"]. createInstance(Components.interfaces.nsIConverterOutputStream); converter.init(foStream, "UTF-8", 0, 0); converter.writeString(data); converter.close(); // this closes foStream } function getTempFile(path) { let parts = [TEMP_FOLDER_NAME]; parts = parts.concat(path.split("/")); return FileUtils.getFile("TmpD", parts); } // https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O#Writing_to_a_file function* getFileData(file) { if (typeof file === "string") { file = new FileUtils.File(file); } let def = promise.defer(); NetUtil.asyncFetch({ uri: NetUtil.newURI(file), loadUsingSystemPrincipal: true }, function (inputStream, status) { if (!Components.isSuccessCode(status)) { info("ERROR READING TEMP FILE", status); } // Detect if an empty file is loaded try { inputStream.available(); } catch (e) { def.resolve(""); return; } var data = NetUtil.readInputStreamToString(inputStream, inputStream.available()); def.resolve(data); }); return def.promise; } /** * Rename the resource of the provided container using the context menu. * * @param {ProjectEditor} projecteditor the current project editor instance * @param {Shell} container for the resource to rename * @param {String} newName the name to use for renaming the resource * @return {Promise} a promise that resolves when the resource has been renamed */ var renameWithContextMenu = Task.async(function* (projecteditor, container, newName) { let popup = projecteditor.contextMenuPopup; let resource = container.resource; info("Going to attempt renaming for: " + resource.path); let waitForPopupShow = onPopupShow(popup); openContextMenu(container.label); yield waitForPopupShow; let renameCommand = popup.querySelector("[command=cmd-rename]"); ok(renameCommand, "Rename command exists in popup"); is(renameCommand.getAttribute("hidden"), "", "Rename command is visible"); is(renameCommand.getAttribute("disabled"), "", "Rename command is enabled"); renameCommand.click(); popup.hidePopup(); let input = container.elt.childNodes[0].childNodes[1]; input.value = resource.basename + newName; let waitForProjectRefresh = onceProjectRefreshed(projecteditor); EventUtils.synthesizeKey("VK_RETURN", {}, projecteditor.window); yield waitForProjectRefresh; try { yield OS.File.stat(resource.path + newName); ok(true, "File is renamed"); } catch (e) { ok(false, "Failed to rename file"); } }); function onceEditorCreated(projecteditor) { let def = promise.defer(); projecteditor.once("onEditorCreated", (editor) => { def.resolve(editor); }); return def.promise; } function onceEditorLoad(projecteditor) { let def = promise.defer(); projecteditor.once("onEditorLoad", (editor) => { def.resolve(editor); }); return def.promise; } function onceEditorActivated(projecteditor) { let def = promise.defer(); projecteditor.once("onEditorActivated", (editor) => { def.resolve(editor); }); return def.promise; } function onceEditorSave(projecteditor) { let def = promise.defer(); projecteditor.once("onEditorSave", (editor, resource) => { def.resolve(resource); }); return def.promise; } function onceProjectRefreshed(projecteditor) { return new Promise(resolve => { projecteditor.project.on("refresh-complete", function refreshComplete() { projecteditor.project.off("refresh-complete", refreshComplete); resolve(); }); }); } function onPopupShow(menu) { let defer = promise.defer(); menu.addEventListener("popupshown", function onpopupshown() { menu.removeEventListener("popupshown", onpopupshown); defer.resolve(); }); return defer.promise; } function onPopupHidden(menu) { let defer = promise.defer(); menu.addEventListener("popuphidden", function onpopuphidden() { menu.removeEventListener("popuphidden", onpopuphidden); defer.resolve(); }); return defer.promise; } function openContextMenu(node) { EventUtils.synthesizeMouseAtCenter( node, {button: 2, type: "contextmenu"}, node.ownerDocument.defaultView ); }