|
1 /* vim:set ts=2 sw=2 sts=2 et: */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 "use strict"; |
|
7 |
|
8 this.EXPORTED_SYMBOLS = [ |
|
9 "_", |
|
10 "assert", |
|
11 "log", |
|
12 "text", |
|
13 "wire", |
|
14 "showFilePicker" |
|
15 ]; |
|
16 |
|
17 const Cc = Components.classes; |
|
18 const Ci = Components.interfaces; |
|
19 const Cu = Components.utils; |
|
20 |
|
21 Cu.import("resource://gre/modules/Services.jsm"); |
|
22 |
|
23 const PROPERTIES_URL = "chrome://browser/locale/devtools/styleeditor.properties"; |
|
24 |
|
25 const console = Services.console; |
|
26 const gStringBundle = Services.strings.createBundle(PROPERTIES_URL); |
|
27 |
|
28 |
|
29 /** |
|
30 * Returns a localized string with the given key name from the string bundle. |
|
31 * |
|
32 * @param aName |
|
33 * @param ...rest |
|
34 * Optional arguments to format in the string. |
|
35 * @return string |
|
36 */ |
|
37 this._ = function _(aName) |
|
38 { |
|
39 |
|
40 if (arguments.length == 1) { |
|
41 return gStringBundle.GetStringFromName(aName); |
|
42 } |
|
43 let rest = Array.prototype.slice.call(arguments, 1); |
|
44 return gStringBundle.formatStringFromName(aName, rest, rest.length); |
|
45 } |
|
46 |
|
47 /** |
|
48 * Assert an expression is true or throw if false. |
|
49 * |
|
50 * @param aExpression |
|
51 * @param aMessage |
|
52 * Optional message. |
|
53 * @return aExpression |
|
54 */ |
|
55 this.assert = function assert(aExpression, aMessage) |
|
56 { |
|
57 if (!!!(aExpression)) { |
|
58 let msg = aMessage ? "ASSERTION FAILURE:" + aMessage : "ASSERTION FAILURE"; |
|
59 log(msg); |
|
60 throw new Error(msg); |
|
61 } |
|
62 return aExpression; |
|
63 } |
|
64 |
|
65 /** |
|
66 * Retrieve or set the text content of an element. |
|
67 * |
|
68 * @param DOMElement aRoot |
|
69 * The element to use for querySelector. |
|
70 * @param string aSelector |
|
71 * Selector string for the element to get/set the text content. |
|
72 * @param string aText |
|
73 * Optional text to set. |
|
74 * @return string |
|
75 * Text content of matching element or null if there were no element |
|
76 * matching aSelector. |
|
77 */ |
|
78 this.text = function text(aRoot, aSelector, aText) |
|
79 { |
|
80 let element = aRoot.querySelector(aSelector); |
|
81 if (!element) { |
|
82 return null; |
|
83 } |
|
84 |
|
85 if (aText === undefined) { |
|
86 return element.textContent; |
|
87 } |
|
88 element.textContent = aText; |
|
89 return aText; |
|
90 } |
|
91 |
|
92 /** |
|
93 * Iterates _own_ properties of an object. |
|
94 * |
|
95 * @param aObject |
|
96 * The object to iterate. |
|
97 * @param function aCallback(aKey, aValue) |
|
98 */ |
|
99 function forEach(aObject, aCallback) |
|
100 { |
|
101 for (let key in aObject) { |
|
102 if (aObject.hasOwnProperty(key)) { |
|
103 aCallback(key, aObject[key]); |
|
104 } |
|
105 } |
|
106 } |
|
107 |
|
108 /** |
|
109 * Log a message to the console. |
|
110 * |
|
111 * @param ...rest |
|
112 * One or multiple arguments to log. |
|
113 * If multiple arguments are given, they will be joined by " " in the log. |
|
114 */ |
|
115 this.log = function log() |
|
116 { |
|
117 console.logStringMessage(Array.prototype.slice.call(arguments).join(" ")); |
|
118 } |
|
119 |
|
120 /** |
|
121 * Wire up element(s) matching selector with attributes, event listeners, etc. |
|
122 * |
|
123 * @param DOMElement aRoot |
|
124 * The element to use for querySelectorAll. |
|
125 * Can be null if aSelector is a DOMElement. |
|
126 * @param string|DOMElement aSelectorOrElement |
|
127 * Selector string or DOMElement for the element(s) to wire up. |
|
128 * @param object aDescriptor |
|
129 * An object describing how to wire matching selector, supported properties |
|
130 * are "events" and "attributes" taking objects themselves. |
|
131 * Each key of properties above represents the name of the event or |
|
132 * attribute, with the value being a function used as an event handler or |
|
133 * string to use as attribute value. |
|
134 * If aDescriptor is a function, the argument is equivalent to : |
|
135 * {events: {'click': aDescriptor}} |
|
136 */ |
|
137 this.wire = function wire(aRoot, aSelectorOrElement, aDescriptor) |
|
138 { |
|
139 let matches; |
|
140 if (typeof(aSelectorOrElement) == "string") { // selector |
|
141 matches = aRoot.querySelectorAll(aSelectorOrElement); |
|
142 if (!matches.length) { |
|
143 return; |
|
144 } |
|
145 } else { |
|
146 matches = [aSelectorOrElement]; // element |
|
147 } |
|
148 |
|
149 if (typeof(aDescriptor) == "function") { |
|
150 aDescriptor = {events: {click: aDescriptor}}; |
|
151 } |
|
152 |
|
153 for (let i = 0; i < matches.length; i++) { |
|
154 let element = matches[i]; |
|
155 forEach(aDescriptor.events, function (aName, aHandler) { |
|
156 element.addEventListener(aName, aHandler, false); |
|
157 }); |
|
158 forEach(aDescriptor.attributes, element.setAttribute); |
|
159 } |
|
160 } |
|
161 |
|
162 /** |
|
163 * Show file picker and return the file user selected. |
|
164 * |
|
165 * @param mixed file |
|
166 * Optional nsIFile or string representing the filename to auto-select. |
|
167 * @param boolean toSave |
|
168 * If true, the user is selecting a filename to save. |
|
169 * @param nsIWindow parentWindow |
|
170 * Optional parent window. If null the parent window of the file picker |
|
171 * will be the window of the attached input element. |
|
172 * @param callback |
|
173 * The callback method, which will be called passing in the selected |
|
174 * file or null if the user did not pick one. |
|
175 * @param AString suggestedFilename |
|
176 * The suggested filename when toSave is true. |
|
177 */ |
|
178 this.showFilePicker = function showFilePicker(path, toSave, parentWindow, |
|
179 callback, suggestedFilename) |
|
180 { |
|
181 if (typeof(path) == "string") { |
|
182 try { |
|
183 if (Services.io.extractScheme(path) == "file") { |
|
184 let uri = Services.io.newURI(path, null, null); |
|
185 let file = uri.QueryInterface(Ci.nsIFileURL).file; |
|
186 callback(file); |
|
187 return; |
|
188 } |
|
189 } catch (ex) { |
|
190 callback(null); |
|
191 return; |
|
192 } |
|
193 try { |
|
194 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); |
|
195 file.initWithPath(path); |
|
196 callback(file); |
|
197 return; |
|
198 } catch (ex) { |
|
199 callback(null); |
|
200 return; |
|
201 } |
|
202 } |
|
203 if (path) { // "path" is an nsIFile |
|
204 callback(path); |
|
205 return; |
|
206 } |
|
207 |
|
208 let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); |
|
209 let mode = toSave ? fp.modeSave : fp.modeOpen; |
|
210 let key = toSave ? "saveStyleSheet" : "importStyleSheet"; |
|
211 let fpCallback = function(result) { |
|
212 if (result == Ci.nsIFilePicker.returnCancel) { |
|
213 callback(null); |
|
214 } else { |
|
215 callback(fp.file); |
|
216 } |
|
217 }; |
|
218 |
|
219 if (toSave && suggestedFilename) { |
|
220 fp.defaultString = suggestedFilename; |
|
221 } |
|
222 |
|
223 fp.init(parentWindow, _(key + ".title"), mode); |
|
224 fp.appendFilters(_(key + ".filter"), "*.css"); |
|
225 fp.appendFilters(fp.filterAll); |
|
226 fp.open(fpCallback); |
|
227 return; |
|
228 } |