|
1 # This Source Code Form is subject to the terms of the Mozilla Public |
|
2 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
4 |
|
5 import os |
|
6 import xml.dom.minidom |
|
7 import StringIO |
|
8 |
|
9 RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
|
10 EM_NS = "http://www.mozilla.org/2004/em-rdf#" |
|
11 |
|
12 class RDF(object): |
|
13 def __str__(self): |
|
14 # real files have an .encoding attribute and use it when you |
|
15 # write() unicode into them: they read()/write() unicode and |
|
16 # put encoded bytes in the backend file. StringIO objects |
|
17 # read()/write() unicode and put unicode in the backing store, |
|
18 # so we must encode the output of getvalue() to get a |
|
19 # bytestring. (cStringIO objects are weirder: they effectively |
|
20 # have .encoding hardwired to "ascii" and put only bytes in |
|
21 # the backing store, so we can't use them here). |
|
22 # |
|
23 # The encoding= argument to dom.writexml() merely sets the XML header's |
|
24 # encoding= attribute. It still writes unencoded unicode to the output file, |
|
25 # so we have to encode it for real afterwards. |
|
26 # |
|
27 # Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660 |
|
28 |
|
29 buf = StringIO.StringIO() |
|
30 self.dom.writexml(buf, encoding="utf-8") |
|
31 return buf.getvalue().encode('utf-8') |
|
32 |
|
33 class RDFUpdate(RDF): |
|
34 def __init__(self): |
|
35 impl = xml.dom.minidom.getDOMImplementation() |
|
36 self.dom = impl.createDocument(RDF_NS, "RDF", None) |
|
37 self.dom.documentElement.setAttribute("xmlns", RDF_NS) |
|
38 self.dom.documentElement.setAttribute("xmlns:em", EM_NS) |
|
39 |
|
40 def _make_node(self, name, value, parent): |
|
41 elem = self.dom.createElement(name) |
|
42 elem.appendChild(self.dom.createTextNode(value)) |
|
43 parent.appendChild(elem) |
|
44 return elem |
|
45 |
|
46 def add(self, manifest, update_link): |
|
47 desc = self.dom.createElement("Description") |
|
48 desc.setAttribute( |
|
49 "about", |
|
50 "urn:mozilla:extension:%s" % manifest.get("em:id") |
|
51 ) |
|
52 self.dom.documentElement.appendChild(desc) |
|
53 |
|
54 updates = self.dom.createElement("em:updates") |
|
55 desc.appendChild(updates) |
|
56 |
|
57 seq = self.dom.createElement("Seq") |
|
58 updates.appendChild(seq) |
|
59 |
|
60 li = self.dom.createElement("li") |
|
61 seq.appendChild(li) |
|
62 |
|
63 li_desc = self.dom.createElement("Description") |
|
64 li.appendChild(li_desc) |
|
65 |
|
66 self._make_node("em:version", manifest.get("em:version"), |
|
67 li_desc) |
|
68 |
|
69 apps = manifest.dom.documentElement.getElementsByTagName( |
|
70 "em:targetApplication" |
|
71 ) |
|
72 |
|
73 for app in apps: |
|
74 target_app = self.dom.createElement("em:targetApplication") |
|
75 li_desc.appendChild(target_app) |
|
76 |
|
77 ta_desc = self.dom.createElement("Description") |
|
78 target_app.appendChild(ta_desc) |
|
79 |
|
80 for name in ["em:id", "em:minVersion", "em:maxVersion"]: |
|
81 elem = app.getElementsByTagName(name)[0] |
|
82 self._make_node(name, elem.firstChild.nodeValue, ta_desc) |
|
83 |
|
84 self._make_node("em:updateLink", update_link, ta_desc) |
|
85 |
|
86 class RDFManifest(RDF): |
|
87 def __init__(self, path): |
|
88 self.dom = xml.dom.minidom.parse(path) |
|
89 |
|
90 def set(self, property, value): |
|
91 elements = self.dom.documentElement.getElementsByTagName(property) |
|
92 if not elements: |
|
93 raise ValueError("Element with value not found: %s" % property) |
|
94 if not elements[0].firstChild: |
|
95 elements[0].appendChild(self.dom.createTextNode(value)) |
|
96 else: |
|
97 elements[0].firstChild.nodeValue = value |
|
98 |
|
99 def get(self, property, default=None): |
|
100 elements = self.dom.documentElement.getElementsByTagName(property) |
|
101 if not elements: |
|
102 return default |
|
103 return elements[0].firstChild.nodeValue |
|
104 |
|
105 def remove(self, property): |
|
106 elements = self.dom.documentElement.getElementsByTagName(property) |
|
107 if not elements: |
|
108 return True |
|
109 else: |
|
110 for i in elements: |
|
111 i.parentNode.removeChild(i); |
|
112 |
|
113 return True; |
|
114 |
|
115 def gen_manifest(template_root_dir, target_cfg, jid, |
|
116 update_url=None, bootstrap=True, enable_mobile=False): |
|
117 install_rdf = os.path.join(template_root_dir, "install.rdf") |
|
118 manifest = RDFManifest(install_rdf) |
|
119 dom = manifest.dom |
|
120 |
|
121 manifest.set("em:id", jid) |
|
122 manifest.set("em:version", |
|
123 target_cfg.get('version', '1.0')) |
|
124 manifest.set("em:name", |
|
125 target_cfg.get('title', target_cfg.get('fullName', target_cfg['name']))) |
|
126 manifest.set("em:description", |
|
127 target_cfg.get("description", "")) |
|
128 manifest.set("em:creator", |
|
129 target_cfg.get("author", "")) |
|
130 manifest.set("em:bootstrap", str(bootstrap).lower()) |
|
131 # XPIs remain packed by default, but package.json can override that. The |
|
132 # RDF format accepts "true" as True, anything else as False. We expect |
|
133 # booleans in the .json file, not strings. |
|
134 manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false") |
|
135 |
|
136 for translator in target_cfg.get("translators", [ ]): |
|
137 elem = dom.createElement("em:translator"); |
|
138 elem.appendChild(dom.createTextNode(translator)) |
|
139 dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) |
|
140 |
|
141 for contributor in target_cfg.get("contributors", [ ]): |
|
142 elem = dom.createElement("em:contributor"); |
|
143 elem.appendChild(dom.createTextNode(contributor)) |
|
144 dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) |
|
145 |
|
146 if update_url: |
|
147 manifest.set("em:updateURL", update_url) |
|
148 else: |
|
149 manifest.remove("em:updateURL") |
|
150 |
|
151 if target_cfg.get("preferences"): |
|
152 manifest.set("em:optionsType", "2") |
|
153 else: |
|
154 manifest.remove("em:optionsType") |
|
155 |
|
156 if enable_mobile: |
|
157 target_app = dom.createElement("em:targetApplication") |
|
158 dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app) |
|
159 |
|
160 ta_desc = dom.createElement("Description") |
|
161 target_app.appendChild(ta_desc) |
|
162 |
|
163 elem = dom.createElement("em:id") |
|
164 elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}")) |
|
165 ta_desc.appendChild(elem) |
|
166 |
|
167 elem = dom.createElement("em:minVersion") |
|
168 elem.appendChild(dom.createTextNode("26.0")) |
|
169 ta_desc.appendChild(elem) |
|
170 |
|
171 elem = dom.createElement("em:maxVersion") |
|
172 elem.appendChild(dom.createTextNode("30.0a1")) |
|
173 ta_desc.appendChild(elem) |
|
174 |
|
175 if target_cfg.get("homepage"): |
|
176 manifest.set("em:homepageURL", target_cfg.get("homepage")) |
|
177 else: |
|
178 manifest.remove("em:homepageURL") |
|
179 |
|
180 return manifest |
|
181 |
|
182 if __name__ == "__main__": |
|
183 print "Running smoke test." |
|
184 root = os.path.join(os.path.dirname(__file__), '../../app-extension') |
|
185 manifest = gen_manifest(root, {'name': 'test extension'}, |
|
186 'fakeid', 'http://foo.com/update.rdf') |
|
187 update = RDFUpdate() |
|
188 update.add(manifest, "https://foo.com/foo.xpi") |
|
189 exercise_str = str(manifest) + str(update) |
|
190 for tagname in ["em:targetApplication", "em:version", "em:id"]: |
|
191 if not len(update.dom.getElementsByTagName(tagname)): |
|
192 raise Exception("tag does not exist: %s" % tagname) |
|
193 if not update.dom.getElementsByTagName(tagname)[0].firstChild: |
|
194 raise Exception("tag has no children: %s" % tagname) |
|
195 print "Success!" |