Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Side by Side Diff: xfa/fxjse/value.cpp

Issue 2043153002: Remove various FXJSE Value methods. (Closed) Base URL: https://pdfium.googlesource.com/pdfium.git@master
Patch Set: Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « xfa/fxjse/value.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2014 PDFium Authors. All rights reserved. 1 // Copyright 2014 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com 5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6 6
7 #include "xfa/fxjse/value.h" 7 #include "xfa/fxjse/value.h"
8 8
9 #include <math.h> 9 #include <math.h>
10 10
(...skipping 25 matching lines...) Expand all
36 } 36 }
37 37
38 FX_BOOL FXJSE_Value_IsArray(CFXJSE_Value* pValue) { 38 FX_BOOL FXJSE_Value_IsArray(CFXJSE_Value* pValue) {
39 return pValue && pValue->IsArray(); 39 return pValue && pValue->IsArray();
40 } 40 }
41 41
42 FX_BOOL FXJSE_Value_IsFunction(CFXJSE_Value* pValue) { 42 FX_BOOL FXJSE_Value_IsFunction(CFXJSE_Value* pValue) {
43 return pValue && pValue->IsFunction(); 43 return pValue && pValue->IsFunction();
44 } 44 }
45 45
46 FX_BOOL FXJSE_Value_ToBoolean(CFXJSE_Value* pValue) {
47 return pValue->ToBoolean();
48 }
49
50 FX_FLOAT FXJSE_Value_ToFloat(CFXJSE_Value* pValue) {
51 return pValue->ToFloat();
52 }
53
54 double FXJSE_Value_ToDouble(CFXJSE_Value* pValue) {
55 return pValue->ToDouble();
56 }
57
58 void FXJSE_Value_ToUTF8String(CFXJSE_Value* pValue,
59 CFX_ByteString& szStrOutput) {
60 pValue->ToString(szStrOutput);
61 }
62
63 int32_t FXJSE_Value_ToInteger(CFXJSE_Value* pValue) {
64 return pValue->ToInteger();
65 }
66
67 void FXJSE_Value_SetUndefined(CFXJSE_Value* pValue) {
68 pValue->SetUndefined();
69 }
70
71 void FXJSE_Value_SetNull(CFXJSE_Value* pValue) {
72 pValue->SetNull();
73 }
74
75 void FXJSE_Value_SetBoolean(CFXJSE_Value* pValue, FX_BOOL bBoolean) {
76 pValue->SetBoolean(bBoolean);
77 }
78
79 void FXJSE_Value_SetUTF8String(CFXJSE_Value* pValue,
80 const CFX_ByteStringC& szString) {
81 pValue->SetString(szString);
82 }
83
84 void FXJSE_Value_SetInteger(CFXJSE_Value* pValue, int32_t nInteger) {
85 pValue->SetInteger(nInteger);
86 }
87
88 void FXJSE_Value_SetFloat(CFXJSE_Value* pValue, FX_FLOAT fFloat) {
89 pValue->SetFloat(fFloat);
90 }
91
92 void FXJSE_Value_SetDouble(CFXJSE_Value* pValue, double dDouble) {
93 pValue->SetDouble(dDouble);
94 }
95
96 void FXJSE_Value_SetObject(CFXJSE_Value* pValue,
97 CFXJSE_HostObject* lpObject,
98 CFXJSE_Class* pClass) {
99 if (!pClass) {
100 ASSERT(!lpObject);
101 pValue->SetJSObject();
102 return;
103 }
104 pValue->SetHostObject(lpObject, pClass);
105 }
106
107 void FXJSE_Value_SetArray(CFXJSE_Value* pValue,
108 uint32_t uValueCount,
109 CFXJSE_Value** rgValues) {
110 pValue->SetArray(uValueCount, rgValues);
111 }
112
113 void FXJSE_Value_Set(CFXJSE_Value* pValue, CFXJSE_Value* pOriginalValue) {
114 ASSERT(pOriginalValue);
115 pValue->Assign(pOriginalValue);
116 }
117
118 FX_BOOL FXJSE_Value_GetObjectProp(CFXJSE_Value* pValue,
119 const CFX_ByteStringC& szPropName,
120 CFXJSE_Value* pPropValue) {
121 ASSERT(pPropValue);
122 return pValue->GetObjectProperty(szPropName, pPropValue);
123 }
124
125 FX_BOOL FXJSE_Value_SetObjectProp(CFXJSE_Value* pValue,
126 const CFX_ByteStringC& szPropName,
127 CFXJSE_Value* pPropValue) {
128 ASSERT(pPropValue);
129 return pValue->SetObjectProperty(szPropName, pPropValue);
130 }
131
132 FX_BOOL FXJSE_Value_GetObjectPropByIdx(CFXJSE_Value* pValue,
133 uint32_t uPropIdx,
134 CFXJSE_Value* pPropValue) {
135 ASSERT(pPropValue);
136 return pValue->GetObjectProperty(uPropIdx, pPropValue);
137 }
138
139 FX_BOOL FXJSE_Value_DeleteObjectProp(CFXJSE_Value* pValue,
140 const CFX_ByteStringC& szPropName) {
141 return pValue->DeleteObjectProperty(szPropName);
142 }
143
144 FX_BOOL FXJSE_Value_ObjectHasOwnProp(CFXJSE_Value* pValue,
145 const CFX_ByteStringC& szPropName,
146 FX_BOOL bUseTypeGetter) {
147 return pValue->HasObjectOwnProperty(szPropName, bUseTypeGetter);
148 }
149
150 FX_BOOL FXJSE_Value_SetObjectOwnProp(CFXJSE_Value* pValue,
151 const CFX_ByteStringC& szPropName,
152 CFXJSE_Value* pPropValue) {
153 ASSERT(pPropValue);
154 return pValue->SetObjectOwnProperty(szPropName, pPropValue);
155 }
156
157 FX_BOOL FXJSE_Value_SetFunctionBind(CFXJSE_Value* pValue,
158 CFXJSE_Value* pOldFunction,
159 CFXJSE_Value* pNewThis) {
160 ASSERT(pOldFunction && pNewThis);
161 return pValue->SetFunctionBind(pOldFunction, pNewThis);
162 }
163
164 void FXJSE_ThrowMessage(const CFX_ByteStringC& utf8Name, 46 void FXJSE_ThrowMessage(const CFX_ByteStringC& utf8Name,
165 const CFX_ByteStringC& utf8Message) { 47 const CFX_ByteStringC& utf8Message) {
166 v8::Isolate* pIsolate = v8::Isolate::GetCurrent(); 48 v8::Isolate* pIsolate = v8::Isolate::GetCurrent();
167 ASSERT(pIsolate); 49 ASSERT(pIsolate);
168 50
169 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(pIsolate); 51 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(pIsolate);
170 v8::Local<v8::String> hMessage = v8::String::NewFromUtf8( 52 v8::Local<v8::String> hMessage = v8::String::NewFromUtf8(
171 pIsolate, utf8Message.c_str(), v8::String::kNormalString, 53 pIsolate, utf8Message.c_str(), v8::String::kNormalString,
172 utf8Message.GetLength()); 54 utf8Message.GetLength());
173 v8::Local<v8::Value> hError; 55 v8::Local<v8::Value> hError;
(...skipping 25 matching lines...) Expand all
199 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 81 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
200 v8::Local<v8::Value> pValue = v8::Local<v8::Value>::New(m_pIsolate, m_hValue); 82 v8::Local<v8::Value> pValue = v8::Local<v8::Value>::New(m_pIsolate, m_hValue);
201 ASSERT(!pValue.IsEmpty()); 83 ASSERT(!pValue.IsEmpty());
202 84
203 if (!pValue->IsObject()) 85 if (!pValue->IsObject())
204 return nullptr; 86 return nullptr;
205 87
206 return FXJSE_RetrieveObjectBinding(pValue.As<v8::Object>(), lpClass); 88 return FXJSE_RetrieveObjectBinding(pValue.As<v8::Object>(), lpClass);
207 } 89 }
208 90
209 V8_INLINE static double FXJSE_ftod(FX_FLOAT fNumber) { 91 void CFXJSE_Value::SetObject(CFXJSE_HostObject* lpObject,
210 if (sizeof(FX_FLOAT) != 4) { 92 CFXJSE_Class* pClass) {
211 ASSERT(FALSE); 93 if (!pClass) {
212 return fNumber; 94 ASSERT(!lpObject);
95 SetJSObject();
96 return;
213 } 97 }
214 98 SetHostObject(lpObject, pClass);
215 uint32_t nFloatBits = (uint32_t&)fNumber;
216 uint8_t nExponent = (uint8_t)(nFloatBits >> 16 >> 7);
217 if (nExponent == 0 || nExponent == 255)
218 return fNumber;
219
220 int8_t nErrExp = nExponent - 127 - 23;
221 if (nErrExp >= 0)
222 return fNumber;
223
224 double dwError = pow(2.0, nErrExp), dwErrorHalf = dwError / 2;
225 double dNumber = fNumber, dNumberAbs = fabs(fNumber);
226 double dNumberAbsMin = dNumberAbs - dwErrorHalf,
227 dNumberAbsMax = dNumberAbs + dwErrorHalf;
228 int32_t iErrPos = 0;
229 if (floor(dNumberAbsMin) == floor(dNumberAbsMax)) {
230 dNumberAbsMin = fmod(dNumberAbsMin, 1.0);
231 dNumberAbsMax = fmod(dNumberAbsMax, 1.0);
232 int32_t iErrPosMin = 1, iErrPosMax = 38;
233 do {
234 int32_t iMid = (iErrPosMin + iErrPosMax) / 2;
235 double dPow = pow(10.0, iMid);
236 if (floor(dNumberAbsMin * dPow) == floor(dNumberAbsMax * dPow)) {
237 iErrPosMin = iMid + 1;
238 } else {
239 iErrPosMax = iMid;
240 }
241 } while (iErrPosMin < iErrPosMax);
242 iErrPos = iErrPosMax;
243 }
244 double dPow = pow(10.0, iErrPos);
245 return fNumber < 0 ? ceil(dNumber * dPow - 0.5) / dPow
246 : floor(dNumber * dPow + 0.5) / dPow;
247 }
248
249 void CFXJSE_Value::SetFloat(FX_FLOAT fFloat) {
250 CFXJSE_ScopeUtil_IsolateHandle scope(m_pIsolate);
251 v8::Local<v8::Value> pValue = v8::Number::New(m_pIsolate, FXJSE_ftod(fFloat));
252 m_hValue.Reset(m_pIsolate, pValue);
253 } 99 }
254 100
255 void CFXJSE_Value::SetHostObject(CFXJSE_HostObject* lpObject, 101 void CFXJSE_Value::SetHostObject(CFXJSE_HostObject* lpObject,
256 CFXJSE_Class* lpClass) { 102 CFXJSE_Class* lpClass) {
257 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 103 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
258 ASSERT(lpClass); 104 ASSERT(lpClass);
259 v8::Local<v8::FunctionTemplate> hClass = 105 v8::Local<v8::FunctionTemplate> hClass =
260 v8::Local<v8::FunctionTemplate>::New(m_pIsolate, lpClass->m_hTemplate); 106 v8::Local<v8::FunctionTemplate>::New(m_pIsolate, lpClass->m_hTemplate);
261 v8::Local<v8::Object> hObject = hClass->InstanceTemplate()->NewInstance(); 107 v8::Local<v8::Object> hObject = hClass->InstanceTemplate()->NewInstance();
262 FXJSE_UpdateObjectBinding(hObject, lpObject); 108 FXJSE_UpdateObjectBinding(hObject, lpObject);
(...skipping 15 matching lines...) Expand all
278 } 124 }
279 125
280 void CFXJSE_Value::SetDate(double dDouble) { 126 void CFXJSE_Value::SetDate(double dDouble) {
281 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 127 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
282 v8::Local<v8::Value> hDate = v8::Date::New(m_pIsolate, dDouble); 128 v8::Local<v8::Value> hDate = v8::Date::New(m_pIsolate, dDouble);
283 m_hValue.Reset(m_pIsolate, hDate); 129 m_hValue.Reset(m_pIsolate, hDate);
284 } 130 }
285 131
286 FX_BOOL CFXJSE_Value::SetObjectProperty(const CFX_ByteStringC& szPropName, 132 FX_BOOL CFXJSE_Value::SetObjectProperty(const CFX_ByteStringC& szPropName,
287 CFXJSE_Value* lpPropValue) { 133 CFXJSE_Value* lpPropValue) {
134 ASSERT(lpPropValue);
288 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 135 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
289 v8::Local<v8::Value> hObject = 136 v8::Local<v8::Value> hObject =
290 v8::Local<v8::Value>::New(m_pIsolate, m_hValue); 137 v8::Local<v8::Value>::New(m_pIsolate, m_hValue);
291 if (!hObject->IsObject()) 138 if (!hObject->IsObject())
292 return FALSE; 139 return FALSE;
293 140
294 v8::Local<v8::Value> hPropValue = 141 v8::Local<v8::Value> hPropValue =
295 v8::Local<v8::Value>::New(m_pIsolate, lpPropValue->DirectGetValue()); 142 v8::Local<v8::Value>::New(m_pIsolate, lpPropValue->DirectGetValue());
296 return (FX_BOOL)hObject.As<v8::Object>()->Set( 143 return (FX_BOOL)hObject.As<v8::Object>()->Set(
297 v8::String::NewFromUtf8(m_pIsolate, szPropName.c_str(), 144 v8::String::NewFromUtf8(m_pIsolate, szPropName.c_str(),
298 v8::String::kNormalString, 145 v8::String::kNormalString,
299 szPropName.GetLength()), 146 szPropName.GetLength()),
300 hPropValue); 147 hPropValue);
301 } 148 }
302 149
303 FX_BOOL CFXJSE_Value::GetObjectProperty(const CFX_ByteStringC& szPropName, 150 FX_BOOL CFXJSE_Value::GetObjectProperty(const CFX_ByteStringC& szPropName,
304 CFXJSE_Value* lpPropValue) { 151 CFXJSE_Value* lpPropValue) {
152 ASSERT(lpPropValue);
305 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 153 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
306 v8::Local<v8::Value> hObject = 154 v8::Local<v8::Value> hObject =
307 v8::Local<v8::Value>::New(m_pIsolate, m_hValue); 155 v8::Local<v8::Value>::New(m_pIsolate, m_hValue);
308 if (!hObject->IsObject()) 156 if (!hObject->IsObject())
309 return FALSE; 157 return FALSE;
310 158
311 v8::Local<v8::Value> hPropValue = 159 v8::Local<v8::Value> hPropValue =
312 hObject.As<v8::Object>()->Get(v8::String::NewFromUtf8( 160 hObject.As<v8::Object>()->Get(v8::String::NewFromUtf8(
313 m_pIsolate, szPropName.c_str(), v8::String::kNormalString, 161 m_pIsolate, szPropName.c_str(), v8::String::kNormalString,
314 szPropName.GetLength())); 162 szPropName.GetLength()));
315 lpPropValue->ForceSetValue(hPropValue); 163 lpPropValue->ForceSetValue(hPropValue);
316 return TRUE; 164 return TRUE;
317 } 165 }
318 166
319 FX_BOOL CFXJSE_Value::SetObjectProperty(uint32_t uPropIdx, 167 FX_BOOL CFXJSE_Value::SetObjectProperty(uint32_t uPropIdx,
320 CFXJSE_Value* lpPropValue) { 168 CFXJSE_Value* lpPropValue) {
321 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 169 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
322 v8::Local<v8::Value> hObject = 170 v8::Local<v8::Value> hObject =
323 v8::Local<v8::Value>::New(m_pIsolate, m_hValue); 171 v8::Local<v8::Value>::New(m_pIsolate, m_hValue);
324 if (!hObject->IsObject()) 172 if (!hObject->IsObject())
325 return FALSE; 173 return FALSE;
326 174
327 v8::Local<v8::Value> hPropValue = 175 v8::Local<v8::Value> hPropValue =
328 v8::Local<v8::Value>::New(m_pIsolate, lpPropValue->DirectGetValue()); 176 v8::Local<v8::Value>::New(m_pIsolate, lpPropValue->DirectGetValue());
329 return (FX_BOOL)hObject.As<v8::Object>()->Set(uPropIdx, hPropValue); 177 return (FX_BOOL)hObject.As<v8::Object>()->Set(uPropIdx, hPropValue);
330 } 178 }
331 179
332 FX_BOOL CFXJSE_Value::GetObjectProperty(uint32_t uPropIdx, 180 FX_BOOL CFXJSE_Value::GetObjectPropertyByIdx(uint32_t uPropIdx,
333 CFXJSE_Value* lpPropValue) { 181 CFXJSE_Value* lpPropValue) {
334 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 182 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
335 v8::Local<v8::Value> hObject = 183 v8::Local<v8::Value> hObject =
336 v8::Local<v8::Value>::New(m_pIsolate, m_hValue); 184 v8::Local<v8::Value>::New(m_pIsolate, m_hValue);
337 if (!hObject->IsObject()) 185 if (!hObject->IsObject())
338 return FALSE; 186 return FALSE;
339 187
340 v8::Local<v8::Value> hPropValue = hObject.As<v8::Object>()->Get(uPropIdx); 188 v8::Local<v8::Value> hPropValue = hObject.As<v8::Object>()->Get(uPropIdx);
341 lpPropValue->ForceSetValue(hPropValue); 189 lpPropValue->ForceSetValue(hPropValue);
342 return TRUE; 190 return TRUE;
343 } 191 }
(...skipping 24 matching lines...) Expand all
368 szPropName.GetLength()); 216 szPropName.GetLength());
369 return hObject.As<v8::Object>()->HasRealNamedProperty(hKey) || 217 return hObject.As<v8::Object>()->HasRealNamedProperty(hKey) ||
370 (bUseTypeGetter && 218 (bUseTypeGetter &&
371 hObject.As<v8::Object>() 219 hObject.As<v8::Object>()
372 ->HasOwnProperty(m_pIsolate->GetCurrentContext(), hKey) 220 ->HasOwnProperty(m_pIsolate->GetCurrentContext(), hKey)
373 .FromMaybe(false)); 221 .FromMaybe(false));
374 } 222 }
375 223
376 FX_BOOL CFXJSE_Value::SetObjectOwnProperty(const CFX_ByteStringC& szPropName, 224 FX_BOOL CFXJSE_Value::SetObjectOwnProperty(const CFX_ByteStringC& szPropName,
377 CFXJSE_Value* lpPropValue) { 225 CFXJSE_Value* lpPropValue) {
226 ASSERT(lpPropValue);
378 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 227 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
379 v8::Local<v8::Value> hObject = 228 v8::Local<v8::Value> hObject =
380 v8::Local<v8::Value>::New(m_pIsolate, m_hValue); 229 v8::Local<v8::Value>::New(m_pIsolate, m_hValue);
381 if (!hObject->IsObject()) 230 if (!hObject->IsObject())
382 return FALSE; 231 return FALSE;
383 232
384 v8::Local<v8::Value> pValue = 233 v8::Local<v8::Value> pValue =
385 v8::Local<v8::Value>::New(m_pIsolate, lpPropValue->m_hValue); 234 v8::Local<v8::Value>::New(m_pIsolate, lpPropValue->m_hValue);
386 return hObject.As<v8::Object>() 235 return hObject.As<v8::Object>()
387 ->DefineOwnProperty( 236 ->DefineOwnProperty(
388 m_pIsolate->GetCurrentContext(), 237 m_pIsolate->GetCurrentContext(),
389 v8::String::NewFromUtf8(m_pIsolate, szPropName.c_str(), 238 v8::String::NewFromUtf8(m_pIsolate, szPropName.c_str(),
390 v8::String::kNormalString, 239 v8::String::kNormalString,
391 szPropName.GetLength()), 240 szPropName.GetLength()),
392 pValue) 241 pValue)
393 .FromMaybe(false); 242 .FromMaybe(false);
394 } 243 }
395 244
396 FX_BOOL CFXJSE_Value::SetFunctionBind(CFXJSE_Value* lpOldFunction, 245 FX_BOOL CFXJSE_Value::SetFunctionBind(CFXJSE_Value* lpOldFunction,
397 CFXJSE_Value* lpNewThis) { 246 CFXJSE_Value* lpNewThis) {
247 ASSERT(lpOldFunction && lpNewThis);
248
398 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate); 249 CFXJSE_ScopeUtil_IsolateHandleRootContext scope(m_pIsolate);
399 v8::Local<v8::Value> rgArgs[2]; 250 v8::Local<v8::Value> rgArgs[2];
400 v8::Local<v8::Value> hOldFunction = 251 v8::Local<v8::Value> hOldFunction =
401 v8::Local<v8::Value>::New(m_pIsolate, lpOldFunction->DirectGetValue()); 252 v8::Local<v8::Value>::New(m_pIsolate, lpOldFunction->DirectGetValue());
402 if (hOldFunction.IsEmpty() || !hOldFunction->IsFunction()) 253 if (hOldFunction.IsEmpty() || !hOldFunction->IsFunction())
403 return FALSE; 254 return FALSE;
404 255
405 rgArgs[0] = hOldFunction; 256 rgArgs[0] = hOldFunction;
406 v8::Local<v8::Value> hNewThis = 257 v8::Local<v8::Value> hNewThis =
407 v8::Local<v8::Value>::New(m_pIsolate, lpNewThis->DirectGetValue()); 258 v8::Local<v8::Value>::New(m_pIsolate, lpNewThis->DirectGetValue());
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after
489 if (lpRetValue) 340 if (lpRetValue)
490 lpRetValue->ForceSetValue(hReturnValue); 341 lpRetValue->ForceSetValue(hReturnValue);
491 342
492 if (lpLocalArgs) { 343 if (lpLocalArgs) {
493 for (uint32_t i = 0; i < nArgCount; i++) 344 for (uint32_t i = 0; i < nArgCount; i++)
494 lpLocalArgs[i].~Local(); 345 lpLocalArgs[i].~Local();
495 FX_Free(lpLocalArgs); 346 FX_Free(lpLocalArgs);
496 } 347 }
497 return bRetValue; 348 return bRetValue;
498 } 349 }
OLDNEW
« no previous file with comments | « xfa/fxjse/value.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698