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

Side by Side Diff: third_party/WebKit/Source/modules/nfc/NFC.cpp

Issue 1708543002: [webnfc] Implement push() method in blink nfc module. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@onionsoup_webnfc
Patch Set: Rebased to master. Added back finalization to NFC class. Created 4 years, 8 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
OLDNEW
1 // Copyright 2015 The Chromium Authors. All rights reserved. 1 // Copyright 2015 The Chromium 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 #include "modules/nfc/NFC.h" 5 #include "modules/nfc/NFC.h"
6 6
7 #include "bindings/core/v8/JSONValuesForV8.h"
7 #include "bindings/core/v8/ScriptPromiseResolver.h" 8 #include "bindings/core/v8/ScriptPromiseResolver.h"
9 #include "bindings/core/v8/V8ArrayBuffer.h"
10 #include "core/dom/DOMArrayBuffer.h"
8 #include "core/dom/DOMException.h" 11 #include "core/dom/DOMException.h"
9 #include "core/dom/ExceptionCode.h" 12 #include "core/dom/ExceptionCode.h"
13 #include "core/frame/LocalDOMWindow.h"
14 #include "modules/nfc/NFCError.h"
10 #include "modules/nfc/NFCMessage.h" 15 #include "modules/nfc/NFCMessage.h"
11 #include "modules/nfc/NFCPushOptions.h" 16 #include "modules/nfc/NFCPushOptions.h"
17 #include "platform/MojoHelper.h"
18 #include "public/platform/ServiceRegistry.h"
19
20 namespace {
21 const char kJsonMimePrefix[] = "application/";
22 const char kJsonMimeType[] = "application/json";
23 const char kOpaqueMimeType[] = "application/octet-stream";
24 const char kPlainTextMimeType[] = "text/plain";
25 const char kPlainTextMimePrefix[] = "text/";
26 } // anonymous namespace
27
28 // Mojo type converters
29 namespace mojo {
30
31 using device::wtf::NFCMessage;
32 using device::wtf::NFCMessagePtr;
33 using device::wtf::NFCRecord;
34 using device::wtf::NFCRecordPtr;
35 using device::wtf::NFCRecordType;
36 using device::wtf::NFCPushOptions;
37 using device::wtf::NFCPushOptionsPtr;
38 using device::wtf::NFCPushTarget;
39
40 NFCPushTarget toNFCPushTarget(const WTF::String& target)
41 {
42 if (target == "tag")
43 return NFCPushTarget::TAG;
44
45 if (target == "peer")
46 return NFCPushTarget::PEER;
47
48 return NFCPushTarget::ANY;
49 }
50
51 NFCRecordType toNFCRecordType(const WTF::String& recordType)
52 {
53 if (recordType == "empty")
54 return NFCRecordType::EMPTY;
55
56 if (recordType == "text")
57 return NFCRecordType::TEXT;
58
59 if (recordType == "url")
60 return NFCRecordType::URL;
61
62 if (recordType == "json")
63 return NFCRecordType::JSON;
64
65 if (recordType == "opaque")
66 return NFCRecordType::OPAQUE_RECORD;
67
68 ASSERT_NOT_REACHED();
69 return NFCRecordType::EMPTY;
70 }
71
72 // https://w3c.github.io/web-nfc/#creating-web-nfc-message Step 2.1
73 // If NFCRecord type is not provided, deduct NFCRecord type from JS data type:
74 // String or Number => 'text' record
75 // ArrayBuffer => 'opaque' record
76 // JSON serializable Object => 'json' record
77 NFCRecordType deductRecordTypeFromDataType(const blink::NFCRecord& record)
78 {
79 if (record.hasData()) {
80 v8::Local<v8::Value> value = record.data().v8Value();
81
82 if (value->IsString()
83 || (value->IsNumber() && !std::isnan(value.As<v8::Number>()->Value() ))) {
84 return NFCRecordType::TEXT;
85 }
86
87 if (value->IsObject() && !value->IsArrayBuffer()) {
88 return NFCRecordType::JSON;
89 }
90
91 if (value->IsArrayBuffer()) {
92 return NFCRecordType::OPAQUE_RECORD;
93 }
94 }
95
96 return NFCRecordType::EMPTY;
97 }
98
99 void setMediaType(NFCRecordPtr& recordPtr, const blink::NFCRecord& record, const WTF::String& mediaType)
100 {
101 recordPtr->mediaType = record.hasMediaType() ? record.mediaType() : mediaTyp e;
102 }
103
104 template <>
105 struct TypeConverter<mojo::WTFArray<uint8_t>, WTF::String> {
106 static mojo::WTFArray<uint8_t> Convert(const WTF::String& string)
107 {
108 mojo::WTFArray<uint8_t> array(string.sizeInBytes());
109 const uint8_t* buffer;
110 if (string.is8Bit())
111 buffer = reinterpret_cast<const uint8_t*>(string.characters8());
112 else
113 buffer = reinterpret_cast<const uint8_t*>(string.characters16());
114 memcpy(&array.front(), buffer, string.sizeInBytes());
115 return array;
116 }
117 };
118
119 template <>
120 struct TypeConverter<mojo::WTFArray<uint8_t>, blink::DOMArrayBuffer*> {
121 static mojo::WTFArray<uint8_t> Convert(blink::DOMArrayBuffer* buffer)
122 {
123 mojo::WTFArray<uint8_t> array(buffer->byteLength());
124 memcpy(&array.front(), buffer->data(), buffer->byteLength());
125 return array;
126 }
127 };
128
129 template <>
130 struct TypeConverter<NFCRecordPtr, WTF::String> {
131 static NFCRecordPtr Convert(const WTF::String& string)
132 {
133 NFCRecordPtr record = NFCRecord::New();
134 record->recordType = NFCRecordType::TEXT;
135 record->mediaType = kPlainTextMimeType;
136 record->data = mojo::WTFArray<uint8_t>::From(string);
137 return record;
138 }
139 };
140
141 template <>
142 struct TypeConverter<NFCRecordPtr, blink::DOMArrayBuffer*> {
143 static NFCRecordPtr Convert(blink::DOMArrayBuffer* buffer)
144 {
145 NFCRecordPtr record = NFCRecord::New();
146 record->recordType = NFCRecordType::OPAQUE_RECORD;
147 record->mediaType = kOpaqueMimeType;
148 record->data = mojo::WTFArray<uint8_t>::From(buffer);
149 return record;
150 }
151 };
152
153 template <>
154 struct TypeConverter<NFCMessagePtr, WTF::String> {
155 static NFCMessagePtr Convert(const WTF::String& string)
156 {
157 NFCMessagePtr message = NFCMessage::New();
158 message->data = mojo::WTFArray<NFCRecordPtr>::New(1);
159 message->data[0] = NFCRecord::From(string);
160 return message;
161 }
162 };
163
164 template <>
165 struct TypeConverter<mojo::WTFArray<uint8_t>, blink::ScriptValue> {
166 static mojo::WTFArray<uint8_t> Convert(const blink::ScriptValue& scriptValue )
167 {
168 v8::Local<v8::Value> value = scriptValue.v8Value();
169
170 if (value->IsNumber())
171 return mojo::WTFArray<uint8_t>::From(WTF::String::number(value.As<v8 ::Number>()->Value()));
172
173 if (value->IsString()) {
174 blink::V8StringResource<> stringResource = value;
175 if (stringResource.prepare())
176 return mojo::WTFArray<uint8_t>::From<WTF::String>(stringResource );
177 }
178
179 if (value->IsObject() && !value->IsArrayBuffer()) {
180 RefPtr<blink::JSONValue> jsonResult = blink::toJSONValue(scriptValue .context(), value);
181 if (jsonResult && (jsonResult->getType() == blink::JSONValue::TypeOb ject))
182 return mojo::WTFArray<uint8_t>::From(jsonResult->toJSONString()) ;
183 }
184
185 if (value->IsArrayBuffer())
186 return mojo::WTFArray<uint8_t>::From(blink::V8ArrayBuffer::toImpl(va lue.As<v8::Object>()));
187
188 return nullptr;
189 }
190 };
191
192 template <>
193 struct TypeConverter<NFCRecordPtr, blink::NFCRecord> {
194 static NFCRecordPtr Convert(const blink::NFCRecord& record)
195 {
196 NFCRecordPtr recordPtr = NFCRecord::New();
197
198 if (record.hasRecordType())
199 recordPtr->recordType = toNFCRecordType(record.recordType());
200 else
201 recordPtr->recordType = deductRecordTypeFromDataType(record);
202
203 // If record type is "empty", no need to set media type or data.
204 // https://w3c.github.io/web-nfc/#creating-web-nfc-message
205 if (recordPtr->recordType == NFCRecordType::EMPTY)
206 return recordPtr;
207
208 switch (recordPtr->recordType) {
209 case NFCRecordType::TEXT:
210 case NFCRecordType::URL:
211 setMediaType(recordPtr, record, kPlainTextMimeType);
212 break;
213 case NFCRecordType::JSON:
214 setMediaType(recordPtr, record, kJsonMimeType);
215 break;
216 case NFCRecordType::OPAQUE_RECORD:
217 setMediaType(recordPtr, record, kOpaqueMimeType);
218 break;
219 default:
220 ASSERT_NOT_REACHED();
221 break;
222 }
223
224 recordPtr->data = mojo::WTFArray<uint8_t>::From(record.data());
225
226 // If JS object cannot be converted to uint8_t array, return null,
227 // interrupt NFCMessage conversion algorithm and reject promise with
228 // SyntaxError exception.
229 if (recordPtr->data.is_null())
230 return nullptr;
231
232 return recordPtr;
233 }
234 };
235
236 template <>
237 struct TypeConverter<NFCMessagePtr, blink::NFCMessage> {
238 static NFCMessagePtr Convert(const blink::NFCMessage& message)
239 {
240 NFCMessagePtr messagePtr = NFCMessage::New();
241 messagePtr->url = message.url();
242 messagePtr->data.resize(message.data().size());
243 for (size_t i = 0; i < message.data().size(); ++i) {
244 NFCRecordPtr record = NFCRecord::From(message.data()[i]);
245 if (record.is_null())
246 return nullptr;
247
248 messagePtr->data[i] = std::move(record);
249 }
250 messagePtr->data = mojo::WTFArray<NFCRecordPtr>::From(message.data());
251 return messagePtr;
252 }
253 };
254
255 template <>
256 struct TypeConverter<NFCMessagePtr, blink::DOMArrayBuffer*> {
257 static NFCMessagePtr Convert(blink::DOMArrayBuffer* buffer)
258 {
259 NFCMessagePtr message = NFCMessage::New();
260 message->data = mojo::WTFArray<NFCRecordPtr>::New(1);
261 message->data[0] = NFCRecord::From(buffer);
262 return message;
263 }
264 };
265
266 template <>
267 struct TypeConverter<NFCMessagePtr, blink::NFCPushMessage> {
268 static NFCMessagePtr Convert(const blink::NFCPushMessage& message)
269 {
270 if (message.isString())
271 return NFCMessage::From(message.getAsString());
272
273 if (message.isNFCMessage())
274 return NFCMessage::From(message.getAsNFCMessage());
275
276 if (message.isArrayBuffer())
277 return NFCMessage::From(message.getAsArrayBuffer());
278
279 ASSERT_NOT_REACHED();
280 return nullptr;
281 }
282 };
283
284 template <>
285 struct TypeConverter<NFCPushOptionsPtr, blink::NFCPushOptions> {
286 static NFCPushOptionsPtr Convert(const blink::NFCPushOptions& pushOptions)
287 {
288 NFCPushOptionsPtr pushOptionsPtr = NFCPushOptions::New();
289
290 if (pushOptions.hasTarget())
291 pushOptionsPtr->target = toNFCPushTarget(pushOptions.target());
292 else
293 pushOptionsPtr->target = NFCPushTarget::ANY;
294
295 if (pushOptions.hasTimeout())
296 pushOptionsPtr->timeout = pushOptions.timeout();
297 else
298 pushOptionsPtr->timeout = std::numeric_limits<double>::infinity();
299
300 if (pushOptions.hasIgnoreRead())
301 pushOptionsPtr->ignoreRead = pushOptions.ignoreRead();
302 else
303 pushOptionsPtr->ignoreRead = true;
kenneth.christiansen 2016/04/14 14:29:27 link to spec saying the default is true?
shalamov 2016/04/15 07:56:39 Done.
304
305 return pushOptionsPtr;
306 }
307 };
308
309 } // namespace mojo
12 310
13 namespace blink { 311 namespace blink {
14 312
313
314 namespace {
315
316 bool isValidTextRecord(const NFCRecord& record)
317 {
318 v8::Local<v8::Value> value = record.data().v8Value();
319 if (!value->IsString() && !(value->IsNumber() && !std::isnan(value.As<v8::Nu mber>()->Value())))
320 return false;
321
322 if (record.hasMediaType() && !record.mediaType().startsWith(kPlainTextMimePr efix, TextCaseInsensitive))
323 return false;
324
325 return true;
326 }
327
328 bool isValidURLRecord(const NFCRecord& record)
329 {
330 if (!record.data().v8Value()->IsString())
331 return false;
332
333 return isValidTextRecord(record);
334 }
335
336 bool isValidJSONRecord(const NFCRecord& record)
337 {
338 v8::Local<v8::Value> value = record.data().v8Value();
339 if (!value->IsObject() || value->IsArrayBuffer())
340 return false;
341
342 if (record.hasMediaType() && !record.mediaType().startsWith(kJsonMimePrefix, TextCaseInsensitive))
343 return false;
344
345 return true;
346 }
347
348 bool isValidOpaqueRecord(const NFCRecord& record)
349 {
350 return record.data().v8Value()->IsArrayBuffer();
351 }
352
353 bool isValidNFCRecord(const NFCRecord& record)
354 {
355 device::wtf::NFCRecordType type;
356 if (record.hasRecordType()) {
357 type = mojo::toNFCRecordType(record.recordType());
358 } else {
359 type = mojo::deductRecordTypeFromDataType(record);
360
361 // https://w3c.github.io/web-nfc/#creating-web-nfc-message
362 // If NFCRecord.recordType is not set and record type cannot be deducted
363 // from NFCRecord.data, reject promise with SyntaxError.
364 if (type == device::wtf::NFCRecordType::EMPTY)
365 return false;
366 }
367
368 // Non-empty records must have data.
369 if (!record.hasData() && (type != device::wtf::NFCRecordType::EMPTY))
370 return false;
371
372 switch (type) {
373 case device::wtf::NFCRecordType::TEXT:
374 return isValidTextRecord(record);
375 case device::wtf::NFCRecordType::URL:
376 return isValidURLRecord(record);
377 case device::wtf::NFCRecordType::JSON:
378 return isValidJSONRecord(record);
379 case device::wtf::NFCRecordType::OPAQUE_RECORD:
380 return isValidOpaqueRecord(record);
381 case device::wtf::NFCRecordType::EMPTY:
382 return !record.hasData() && record.mediaType().isEmpty();
383 }
384
385 ASSERT_NOT_REACHED();
386 return false;
387 }
388
389 DOMException* isValidNFCRecordArray(const HeapVector<NFCRecord>& records)
390 {
391 // https://w3c.github.io/web-nfc/#the-push-method
392 // If NFCMessage.data is empty, reject promise with SyntaxError
393 if (records.isEmpty())
394 return DOMException::create(SyntaxError);
395
396 for (const auto& record : records) {
397 if (!isValidNFCRecord(record))
398 return DOMException::create(SyntaxError);
399 }
400
401 return nullptr;
402 }
403
404 DOMException* isValidNFCPushMessage(const NFCPushMessage& message)
405 {
406 if (!message.isNFCMessage() && !message.isString() && !message.isArrayBuffer ())
407 return DOMException::create(TypeMismatchError);
408
409 if (message.isNFCMessage()) {
410 if (!message.getAsNFCMessage().hasData())
411 return DOMException::create(TypeMismatchError);
412
413 return isValidNFCRecordArray(message.getAsNFCMessage().data());
414 }
415
416 return nullptr;
417 }
418
419 bool setURL(const String& origin, device::wtf::NFCMessagePtr& message)
420 {
421 KURL originURL(ParsedURLString, origin);
422
423 if (!message->url.isEmpty() && originURL.canSetPathname()) {
424 originURL.setPath(message->url);
425 message->url = originURL;
426 }
427
428 return originURL.isValid();
429 }
430
431 } // anonymous namespace
432
433 using NFCMojoCallback = mojo::Callback<void(device::wtf::NFCErrorPtr)>;
434 class NFCCallback final : public NFCMojoCallback::Runnable {
435 public:
436 using NFCCallbackPromiseAdapter = CallbackPromiseAdapter<void, NFCError>;
437 NFCCallback(ScriptPromiseResolver* resolver)
438 : m_promiseAdapter(adoptPtr(new NFCCallbackPromiseAdapter(resolver))) { }
439
440 void Run(device::wtf::NFCErrorPtr error)
441 {
442 if (error.is_null())
443 m_promiseAdapter->onSuccess();
444 else
445 m_promiseAdapter->onError(error->error_type);
446 }
447
448 private:
449 OwnPtr<NFCCallbackPromiseAdapter> m_promiseAdapter;
450 };
451
15 NFC::NFC(LocalFrame* frame) 452 NFC::NFC(LocalFrame* frame)
16 : LocalFrameLifecycleObserver(frame) 453 : PageLifecycleObserver(frame ? frame->page() : 0),
17 , PageLifecycleObserver(frame ? frame->page() : 0) 454 m_client(this)
18 { 455 {
19 } 456 }
20 457
21 NFC* NFC::create(LocalFrame* frame) 458 NFC* NFC::create(LocalFrame* frame)
22 { 459 {
23 NFC* nfc = new NFC(frame); 460 NFC* nfc = new NFC(frame);
24 return nfc; 461 return nfc;
25 } 462 }
26 463
27 ScriptPromise NFC::push(ScriptState* scriptState, const NFCPushMessage& records, const NFCPushOptions& options) 464 NFC::~NFC() = default;
465
466 // https://w3c.github.io/web-nfc/#writing-or-pushing-content
467 ScriptPromise NFC::push(ScriptState* scriptState, const NFCPushMessage& pushMess age, const NFCPushOptions& options)
28 { 468 {
29 // TODO(shalamov): To be implemented. 469 String errorMessage;
30 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError)); 470 if (!scriptState->getExecutionContext()->isSecureContext(errorMessage)) {
471 return ScriptPromise::rejectWithDOMException(scriptState, DOMException:: create(SecurityError, errorMessage));
472 }
473
474 DOMException* exception = isValidNFCPushMessage(pushMessage);
475 if (exception)
476 return ScriptPromise::rejectWithDOMException(scriptState, exception);
477
478 if (!Initialize(scriptState->domWindow()->frame()->serviceRegistry()))
479 return ScriptPromise::rejectWithDOMException(scriptState, DOMException:: create(NotSupportedError));
480
481 device::wtf::NFCMessagePtr message = device::wtf::NFCMessage::From(pushMessa ge);
482 if (!message)
483 return ScriptPromise::rejectWithDOMException(scriptState, DOMException:: create(SyntaxError));
484
485 if (!setURL(scriptState->getExecutionContext()->getSecurityOrigin()->toStrin g(), message))
486 return ScriptPromise::rejectWithDOMException(scriptState, DOMException:: create(SyntaxError));
487
488 ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState) ;
489 m_nfc->Push(std::move(message), device::wtf::NFCPushOptions::From(options), device::wtf::NFC::PushCallback(new NFCCallback(resolver)));
490
491 return resolver->promise();
31 } 492 }
32 493
33 ScriptPromise NFC::cancelPush(ScriptState* scriptState, const String& target) 494 ScriptPromise NFC::cancelPush(ScriptState* scriptState, const String& target)
34 { 495 {
35 // TODO(shalamov): To be implemented. 496 String errorMessage;
36 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError)); 497 if (!scriptState->getExecutionContext()->isSecureContext(errorMessage)) {
498 return ScriptPromise::rejectWithDOMException(scriptState, DOMException:: create(SecurityError, errorMessage));
499 }
500
501 if (!Initialize(scriptState->domWindow()->frame()->serviceRegistry()))
502 return ScriptPromise::rejectWithDOMException(scriptState, DOMException:: create(NotSupportedError));
503
504 ScriptPromiseResolver* resolver = ScriptPromiseResolver::create(scriptState) ;
505 m_nfc->CancelPush(mojo::toNFCPushTarget(target), device::wtf::NFC::CancelPus hCallback(new NFCCallback(resolver)));
506
507 return resolver->promise();
37 } 508 }
38 509
39 ScriptPromise NFC::watch(ScriptState* scriptState, MessageCallback* callback, co nst NFCWatchOptions& options) 510 ScriptPromise NFC::watch(ScriptState* scriptState, MessageCallback* callback, co nst NFCWatchOptions& options)
40 { 511 {
41 // TODO(shalamov): To be implemented. 512 // TODO(shalamov): To be implemented.
42 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError)); 513 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError));
43 } 514 }
44 515
45 ScriptPromise NFC::cancelWatch(ScriptState* scriptState, long id) 516 ScriptPromise NFC::cancelWatch(ScriptState* scriptState, long id)
46 { 517 {
47 // TODO(shalamov): To be implemented. 518 // TODO(shalamov): To be implemented.
48 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError)); 519 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError));
49 } 520 }
50 521
51 ScriptPromise NFC::cancelWatch(ScriptState* scriptState) 522 ScriptPromise NFC::cancelWatch(ScriptState* scriptState)
52 { 523 {
53 // TODO(shalamov): To be implemented. 524 // TODO(shalamov): To be implemented.
54 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError)); 525 return ScriptPromise::rejectWithDOMException(scriptState, DOMException::crea te(NotSupportedError));
55 } 526 }
56 527
57 void NFC::willDetachFrameHost()
58 {
59 // TODO(shalamov): To be implemented.
60 }
61
62 void NFC::pageVisibilityChanged() 528 void NFC::pageVisibilityChanged()
63 { 529 {
64 // TODO(shalamov): To be implemented. When visibility is lost, 530 // If service is not initialized, there cannot be any pending NFC activities
531 if (!m_nfc)
532 return;
533
65 // NFC operations should be suspended. 534 // NFC operations should be suspended.
66 // https://w3c.github.io/web-nfc/#nfc-suspended 535 // https://w3c.github.io/web-nfc/#nfc-suspended
536 if (page()->visibilityState() == PageVisibilityStateVisible)
537 m_nfc->ResumeNFCOperations();
538 else
539 m_nfc->SuspendNFCOperations();
540 }
541
542 void NFC::OnWatch(uint32_t id, device::wtf::NFCMessagePtr message)
543 {
544 }
545
546 bool NFC::Initialize(ServiceRegistry* registry)
547 {
548 DCHECK(registry);
549
550 if (m_nfc)
kenneth.christiansen 2016/04/14 14:29:27 do you intent to initialize this multiple times? o
shalamov 2016/04/15 07:56:39 I want to lazy initialize mojo service only when o
551 return true;
552
553 registry->connectToRemoteService(mojo::GetProxy(&m_nfc));
554
555 if (m_nfc) {
556 m_nfc.set_connection_error_handler(sameThreadBindForMojo(&NFC::OnError, this));
557 m_nfc->SetClient(m_client.CreateInterfacePtrAndBind());
558 }
559
560 return m_nfc;
561 }
562
563 void NFC::OnError()
564 {
565 // todo(shalamov): clear map that contains nfc watch callbacks
67 } 566 }
68 567
69 DEFINE_TRACE(NFC) 568 DEFINE_TRACE(NFC)
70 { 569 {
71 LocalFrameLifecycleObserver::trace(visitor);
72 PageLifecycleObserver::trace(visitor); 570 PageLifecycleObserver::trace(visitor);
73 } 571 }
74 572
75 } // namespace blink 573 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698