Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
michael@0 | 1 | /* vim:set ts=2 sw=2 sts=2 et tw=80: |
michael@0 | 2 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | const {Cu} = require("chrome"); |
michael@0 | 8 | const Editor = require("devtools/sourceeditor/editor"); |
michael@0 | 9 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 10 | Cu.import("resource://gre/modules/devtools/event-emitter.js"); |
michael@0 | 11 | |
michael@0 | 12 | exports.HTMLEditor = HTMLEditor; |
michael@0 | 13 | |
michael@0 | 14 | function ctrl(k) { |
michael@0 | 15 | return (Services.appinfo.OS == "Darwin" ? "Cmd-" : "Ctrl-") + k; |
michael@0 | 16 | } |
michael@0 | 17 | function stopPropagation(e) { |
michael@0 | 18 | e.stopPropagation(); |
michael@0 | 19 | } |
michael@0 | 20 | /** |
michael@0 | 21 | * A wrapper around the Editor component, that allows editing of HTML. |
michael@0 | 22 | * |
michael@0 | 23 | * The main functionality this provides around the Editor is the ability |
michael@0 | 24 | * to show/hide/position an editor inplace. It only appends once to the |
michael@0 | 25 | * body, and uses CSS to position the editor. The reason it is done this |
michael@0 | 26 | * way is that the editor is loaded in an iframe, and calling appendChild |
michael@0 | 27 | * causes it to reload. |
michael@0 | 28 | * |
michael@0 | 29 | * Meant to be embedded inside of an HTML page, as in markup-view.xhtml. |
michael@0 | 30 | * |
michael@0 | 31 | * @param HTMLDocument htmlDocument |
michael@0 | 32 | * The document to attach the editor to. Will also use this |
michael@0 | 33 | * document as a basis for listening resize events. |
michael@0 | 34 | */ |
michael@0 | 35 | function HTMLEditor(htmlDocument) |
michael@0 | 36 | { |
michael@0 | 37 | this.doc = htmlDocument; |
michael@0 | 38 | this.container = this.doc.createElement("div"); |
michael@0 | 39 | this.container.className = "html-editor theme-body"; |
michael@0 | 40 | this.container.style.display = "none"; |
michael@0 | 41 | this.editorInner = this.doc.createElement("div"); |
michael@0 | 42 | this.editorInner.className = "html-editor-inner"; |
michael@0 | 43 | this.container.appendChild(this.editorInner); |
michael@0 | 44 | |
michael@0 | 45 | this.doc.body.appendChild(this.container); |
michael@0 | 46 | this.hide = this.hide.bind(this); |
michael@0 | 47 | this.refresh = this.refresh.bind(this); |
michael@0 | 48 | |
michael@0 | 49 | EventEmitter.decorate(this); |
michael@0 | 50 | |
michael@0 | 51 | this.doc.defaultView.addEventListener("resize", |
michael@0 | 52 | this.refresh, true); |
michael@0 | 53 | |
michael@0 | 54 | let config = { |
michael@0 | 55 | mode: Editor.modes.html, |
michael@0 | 56 | lineWrapping: true, |
michael@0 | 57 | styleActiveLine: false, |
michael@0 | 58 | extraKeys: {}, |
michael@0 | 59 | theme: "mozilla markup-view" |
michael@0 | 60 | }; |
michael@0 | 61 | |
michael@0 | 62 | config.extraKeys[ctrl("Enter")] = this.hide; |
michael@0 | 63 | config.extraKeys["F2"] = this.hide; |
michael@0 | 64 | config.extraKeys["Esc"] = this.hide.bind(this, false); |
michael@0 | 65 | |
michael@0 | 66 | this.container.addEventListener("click", this.hide, false); |
michael@0 | 67 | this.editorInner.addEventListener("click", stopPropagation, false); |
michael@0 | 68 | this.editor = new Editor(config); |
michael@0 | 69 | |
michael@0 | 70 | let iframe = this.editorInner.ownerDocument.createElement("iframe"); |
michael@0 | 71 | this.editor.appendTo(this.editorInner, iframe).then(() => { |
michael@0 | 72 | this.hide(false); |
michael@0 | 73 | }).then(null, (err) => console.log(err.message)); |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | HTMLEditor.prototype = { |
michael@0 | 77 | |
michael@0 | 78 | /** |
michael@0 | 79 | * Need to refresh position by manually setting CSS values, so this will |
michael@0 | 80 | * need to be called on resizes and other sizing changes. |
michael@0 | 81 | */ |
michael@0 | 82 | refresh: function() { |
michael@0 | 83 | let element = this._attachedElement; |
michael@0 | 84 | |
michael@0 | 85 | if (element) { |
michael@0 | 86 | this.container.style.top = element.offsetTop + "px"; |
michael@0 | 87 | this.container.style.left = element.offsetLeft + "px"; |
michael@0 | 88 | this.container.style.width = element.offsetWidth + "px"; |
michael@0 | 89 | this.container.style.height = element.parentNode.offsetHeight + "px"; |
michael@0 | 90 | this.editor.refresh(); |
michael@0 | 91 | } |
michael@0 | 92 | }, |
michael@0 | 93 | |
michael@0 | 94 | /** |
michael@0 | 95 | * Anchor the editor to a particular element. |
michael@0 | 96 | * |
michael@0 | 97 | * @param DOMNode element |
michael@0 | 98 | * The element that the editor will be anchored to. |
michael@0 | 99 | * Should belong to the HTMLDocument passed into the constructor. |
michael@0 | 100 | */ |
michael@0 | 101 | _attach: function(element) |
michael@0 | 102 | { |
michael@0 | 103 | this._detach(); |
michael@0 | 104 | this._attachedElement = element; |
michael@0 | 105 | element.classList.add("html-editor-container"); |
michael@0 | 106 | this.refresh(); |
michael@0 | 107 | }, |
michael@0 | 108 | |
michael@0 | 109 | /** |
michael@0 | 110 | * Unanchor the editor from an element. |
michael@0 | 111 | */ |
michael@0 | 112 | _detach: function() |
michael@0 | 113 | { |
michael@0 | 114 | if (this._attachedElement) { |
michael@0 | 115 | this._attachedElement.classList.remove("html-editor-container"); |
michael@0 | 116 | this._attachedElement = undefined; |
michael@0 | 117 | } |
michael@0 | 118 | }, |
michael@0 | 119 | |
michael@0 | 120 | /** |
michael@0 | 121 | * Anchor the editor to a particular element, and show the editor. |
michael@0 | 122 | * |
michael@0 | 123 | * @param DOMNode element |
michael@0 | 124 | * The element that the editor will be anchored to. |
michael@0 | 125 | * Should belong to the HTMLDocument passed into the constructor. |
michael@0 | 126 | * @param string text |
michael@0 | 127 | * Value to set the contents of the editor to |
michael@0 | 128 | * @param function cb |
michael@0 | 129 | * The function to call when hiding |
michael@0 | 130 | */ |
michael@0 | 131 | show: function(element, text) |
michael@0 | 132 | { |
michael@0 | 133 | if (this._visible) { |
michael@0 | 134 | return; |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | this._originalValue = text; |
michael@0 | 138 | this.editor.setText(text); |
michael@0 | 139 | this._attach(element); |
michael@0 | 140 | this.container.style.display = "flex"; |
michael@0 | 141 | this._visible = true; |
michael@0 | 142 | |
michael@0 | 143 | this.editor.refresh(); |
michael@0 | 144 | this.editor.focus(); |
michael@0 | 145 | |
michael@0 | 146 | this.emit("popupshown"); |
michael@0 | 147 | }, |
michael@0 | 148 | |
michael@0 | 149 | /** |
michael@0 | 150 | * Hide the editor, optionally committing the changes |
michael@0 | 151 | * |
michael@0 | 152 | * @param bool shouldCommit |
michael@0 | 153 | * A change will be committed by default. If this param |
michael@0 | 154 | * strictly equals false, no change will occur. |
michael@0 | 155 | */ |
michael@0 | 156 | hide: function(shouldCommit) |
michael@0 | 157 | { |
michael@0 | 158 | if (!this._visible) { |
michael@0 | 159 | return; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | this.container.style.display = "none"; |
michael@0 | 163 | this._detach(); |
michael@0 | 164 | |
michael@0 | 165 | let newValue = this.editor.getText(); |
michael@0 | 166 | let valueHasChanged = this._originalValue !== newValue; |
michael@0 | 167 | let preventCommit = shouldCommit === false || !valueHasChanged; |
michael@0 | 168 | this._originalValue = undefined; |
michael@0 | 169 | this._visible = undefined; |
michael@0 | 170 | this.emit("popuphidden", !preventCommit, newValue); |
michael@0 | 171 | }, |
michael@0 | 172 | |
michael@0 | 173 | /** |
michael@0 | 174 | * Destroy this object and unbind all event handlers |
michael@0 | 175 | */ |
michael@0 | 176 | destroy: function() |
michael@0 | 177 | { |
michael@0 | 178 | this.doc.defaultView.removeEventListener("resize", |
michael@0 | 179 | this.refresh, true); |
michael@0 | 180 | this.container.removeEventListener("click", this.hide, false); |
michael@0 | 181 | this.editorInner.removeEventListener("click", stopPropagation, false); |
michael@0 | 182 | |
michael@0 | 183 | this.hide(false); |
michael@0 | 184 | this.container.parentNode.removeChild(this.container); |
michael@0 | 185 | } |
michael@0 | 186 | }; |