1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/background_contents_service.h"
6
7 #include "base/basictypes.h"
8 #include "base/command_line.h"
9 #include "base/string_util.h"
10 #include "base/utf_string_conversions.h"
11 #include "base/values.h"
12 #include "chrome/browser/background_contents_service_factory.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/extensions/extension_host.h"
15 #include "chrome/browser/extensions/extension_service.h"
16 #include "chrome/browser/notifications/desktop_notification_service.h"
17 #include "chrome/browser/notifications/notification.h"
18 #include "chrome/browser/notifications/notification_ui_manager.h"
19 #include "chrome/browser/prefs/pref_service.h"
20 #include "chrome/browser/prefs/scoped_user_pref_update.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_list.h"
24 #include "chrome/common/chrome_switches.h"
25 #include "chrome/common/extensions/extension.h"
26 #include "chrome/common/pref_names.h"
27 #include "content/browser/renderer_host/render_view_host.h"
28 #include "content/browser/site_instance.h"
29 #include "content/browser/tab_contents/tab_contents.h"
30 #include "content/common/notification_service.h"
31 #include "content/common/notification_type.h"
32 #include "grit/generated_resources.h"
33 #include "ui/base/l10n/l10n_util.h"
34
35 namespace {
36
37 const char kNotificationPrefix[] = "app.background.crashed.";
38
CloseBalloon(const std::string id)39 void CloseBalloon(const std::string id) {
40 g_browser_process->notification_ui_manager()->CancelById(id);
41 }
42
ScheduleCloseBalloon(const std::string & extension_id)43 void ScheduleCloseBalloon(const std::string& extension_id) {
44 MessageLoop::current()->PostTask(FROM_HERE,
45 NewRunnableFunction(&CloseBalloon,
46 kNotificationPrefix + extension_id));
47 }
48
49 class CrashNotificationDelegate : public NotificationDelegate {
50 public:
CrashNotificationDelegate(Profile * profile,const Extension * extension)51 CrashNotificationDelegate(Profile* profile, const Extension* extension)
52 : profile_(profile),
53 is_hosted_app_(extension->is_hosted_app()),
54 extension_id_(extension->id()) {
55 }
56
~CrashNotificationDelegate()57 ~CrashNotificationDelegate() {
58 }
59
Display()60 void Display() {}
61
Error()62 void Error() {}
63
Close(bool by_user)64 void Close(bool by_user) {}
65
Click()66 void Click() {
67 if (is_hosted_app_) {
68 BackgroundContentsServiceFactory::GetForProfile(profile_)->
69 LoadBackgroundContentsForExtension(profile_, extension_id_);
70 } else {
71 profile_->GetExtensionService()->ReloadExtension(extension_id_);
72 }
73
74 // Closing the balloon here should be OK, but it causes a crash on Mac
75 // http://crbug.com/78167
76 ScheduleCloseBalloon(extension_id_);
77 }
78
id() const79 std::string id() const {
80 return kNotificationPrefix + extension_id_;
81 }
82
83 private:
84 Profile* profile_;
85 bool is_hosted_app_;
86 std::string extension_id_;
87
88 DISALLOW_COPY_AND_ASSIGN(CrashNotificationDelegate);
89 };
90
ShowBalloon(const Extension * extension,Profile * profile)91 void ShowBalloon(const Extension* extension, Profile* profile) {
92 string16 message = l10n_util::GetStringFUTF16(
93 extension->is_hosted_app() ? IDS_BACKGROUND_CRASHED_APP_BALLOON_MESSAGE :
94 IDS_BACKGROUND_CRASHED_EXTENSION_BALLOON_MESSAGE,
95 UTF8ToUTF16(extension->name()));
96 string16 content_url = DesktopNotificationService::CreateDataUrl(
97 extension->GetIconURL(Extension::EXTENSION_ICON_SMALLISH,
98 ExtensionIconSet::MATCH_BIGGER),
99 string16(), message, WebKit::WebTextDirectionDefault);
100 Notification notification(
101 extension->url(), GURL(content_url), string16(), string16(),
102 new CrashNotificationDelegate(profile, extension));
103 g_browser_process->notification_ui_manager()->Add(notification, profile);
104 }
105
106 }
107
108 // Keys for the information we store about individual BackgroundContents in
109 // prefs. There is one top-level DictionaryValue (stored at
110 // prefs::kRegisteredBackgroundContents). Information about each
111 // BackgroundContents is stored under that top-level DictionaryValue, keyed
112 // by the parent application ID for easy lookup.
113 //
114 // kRegisteredBackgroundContents:
115 // DictionaryValue {
116 // <appid_1>: { "url": <url1>, "name": <frame_name> },
117 // <appid_2>: { "url": <url2>, "name": <frame_name> },
118 // ... etc ...
119 // }
120 const char kUrlKey[] = "url";
121 const char kFrameNameKey[] = "name";
122
BackgroundContentsService(Profile * profile,const CommandLine * command_line)123 BackgroundContentsService::BackgroundContentsService(
124 Profile* profile, const CommandLine* command_line)
125 : prefs_(NULL) {
126 // Don't load/store preferences if the proper switch is not enabled, or if
127 // the parent profile is incognito.
128 if (!profile->IsOffTheRecord() &&
129 !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents))
130 prefs_ = profile->GetPrefs();
131
132 // Listen for events to tell us when to load/unload persisted background
133 // contents.
134 StartObserving(profile);
135 }
136
~BackgroundContentsService()137 BackgroundContentsService::~BackgroundContentsService() {
138 // BackgroundContents should be shutdown before we go away, as otherwise
139 // our browser process refcount will be off.
140 DCHECK(contents_map_.empty());
141 }
142
143 std::vector<BackgroundContents*>
GetBackgroundContents() const144 BackgroundContentsService::GetBackgroundContents() const
145 {
146 std::vector<BackgroundContents*> contents;
147 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
148 it != contents_map_.end(); ++it)
149 contents.push_back(it->second.contents);
150 return contents;
151 }
152
StartObserving(Profile * profile)153 void BackgroundContentsService::StartObserving(Profile* profile) {
154 // On startup, load our background pages after extension-apps have loaded.
155 registrar_.Add(this, NotificationType::EXTENSIONS_READY,
156 Source<Profile>(profile));
157
158 // Track the lifecycle of all BackgroundContents in the system to allow us
159 // to store an up-to-date list of the urls. Start tracking contents when they
160 // have been opened via CreateBackgroundContents(), and stop tracking them
161 // when they are closed by script.
162 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED,
163 Source<Profile>(profile));
164
165 // Stop tracking BackgroundContents when they have been deleted (happens
166 // during shutdown or if the render process dies).
167 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED,
168 Source<Profile>(profile));
169
170 // Track when the BackgroundContents navigates to a new URL so we can update
171 // our persisted information as appropriate.
172 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
173 Source<Profile>(profile));
174
175 // Listen for new extension installs so that we can load any associated
176 // background page.
177 registrar_.Add(this, NotificationType::EXTENSION_LOADED,
178 Source<Profile>(profile));
179
180 // Track when the extensions crash so that the user can be notified
181 // about it, and the crashed contents can be restarted.
182 registrar_.Add(this, NotificationType::EXTENSION_PROCESS_TERMINATED,
183 Source<Profile>(profile));
184 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_TERMINATED,
185 Source<Profile>(profile));
186
187 // Listen for extensions to be unloaded so we can shutdown associated
188 // BackgroundContents.
189 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
190 Source<Profile>(profile));
191
192 // Make sure the extension-crash balloons are removed when the extension is
193 // uninstalled/reloaded. We cannot do this from UNLOADED since a crashed
194 // extension is unloaded immediately after the crash, not when user reloads or
195 // uninstalls the extension.
196 registrar_.Add(this, NotificationType::EXTENSION_UNINSTALLED,
197 Source<Profile>(profile));
198 }
199
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)200 void BackgroundContentsService::Observe(NotificationType type,
201 const NotificationSource& source,
202 const NotificationDetails& details) {
203 switch (type.value) {
204 case NotificationType::EXTENSIONS_READY:
205 LoadBackgroundContentsFromManifests(Source<Profile>(source).ptr());
206 LoadBackgroundContentsFromPrefs(Source<Profile>(source).ptr());
207 break;
208 case NotificationType::BACKGROUND_CONTENTS_DELETED:
209 BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr());
210 break;
211 case NotificationType::BACKGROUND_CONTENTS_CLOSED:
212 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
213 UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr());
214 break;
215 case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: {
216 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
217
218 // Do not register in the pref if the extension has a manifest-specified
219 // background page.
220 BackgroundContents* bgcontents =
221 Details<BackgroundContents>(details).ptr();
222 Profile* profile = Source<Profile>(source).ptr();
223 const string16& appid = GetParentApplicationId(bgcontents);
224 ExtensionService* extension_service = profile->GetExtensionService();
225 // extension_service can be NULL when running tests.
226 if (extension_service) {
227 const Extension* extension =
228 extension_service->GetExtensionById(UTF16ToUTF8(appid), false);
229 if (extension && extension->background_url().is_valid())
230 break;
231 }
232 RegisterBackgroundContents(Details<BackgroundContents>(details).ptr());
233 break;
234 }
235 case NotificationType::EXTENSION_LOADED: {
236 const Extension* extension = Details<const Extension>(details).ptr();
237 Profile* profile = Source<Profile>(source).ptr();
238 if (extension->is_hosted_app() &&
239 extension->background_url().is_valid()) {
240 // If there is a background page specified in the manifest for a hosted
241 // app, then blow away registered urls in the pref.
242 ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
243
244 ExtensionService* service = profile->GetExtensionService();
245 if (service && service->is_ready()) {
246 // Now load the manifest-specified background page. If service isn't
247 // ready, then the background page will be loaded from the
248 // EXTENSIONS_READY callback.
249 LoadBackgroundContents(profile, extension->background_url(),
250 ASCIIToUTF16("background"), UTF8ToUTF16(extension->id()));
251 }
252 }
253
254 // Remove any "This extension has crashed" balloons.
255 ScheduleCloseBalloon(extension->id());
256 break;
257 }
258 case NotificationType::EXTENSION_PROCESS_TERMINATED:
259 case NotificationType::BACKGROUND_CONTENTS_TERMINATED: {
260 Profile* profile = Source<Profile>(source).ptr();
261 const Extension* extension = NULL;
262 if (type.value == NotificationType::BACKGROUND_CONTENTS_TERMINATED) {
263 BackgroundContents* bg =
264 Details<BackgroundContents>(details).ptr();
265 std::string extension_id = UTF16ToASCII(
266 BackgroundContentsServiceFactory::GetForProfile(profile)->
267 GetParentApplicationId(bg));
268 extension =
269 profile->GetExtensionService()->GetExtensionById(extension_id, false);
270 } else {
271 ExtensionHost* extension_host = Details<ExtensionHost>(details).ptr();
272 extension = extension_host->extension();
273 }
274 if (!extension)
275 break;
276
277 // When an extension crashes, EXTENSION_PROCESS_TERMINATED is followed by
278 // an EXTENSION_UNLOADED notification. This UNLOADED signal causes all the
279 // notifications for this extension to be cancelled by
280 // DesktopNotificationService. For this reason, instead of showing the
281 // balloon right now, we schedule it to show a little later.
282 MessageLoop::current()->PostTask(FROM_HERE,
283 NewRunnableFunction(&ShowBalloon, extension, profile));
284 break;
285 }
286 case NotificationType::EXTENSION_UNLOADED:
287 switch (Details<UnloadedExtensionInfo>(details)->reason) {
288 case UnloadedExtensionInfo::DISABLE: // Intentionally fall through.
289 case UnloadedExtensionInfo::UNINSTALL:
290 ShutdownAssociatedBackgroundContents(
291 ASCIIToUTF16(
292 Details<UnloadedExtensionInfo>(details)->extension->id()));
293 break;
294 case UnloadedExtensionInfo::UPDATE: {
295 // If there is a manifest specified background page, then shut it down
296 // here, since if the updated extension still has the background page,
297 // then it will be loaded from LOADED callback. Otherwise, leave
298 // BackgroundContents in place.
299 const Extension* extension =
300 Details<UnloadedExtensionInfo>(details)->extension;
301 if (extension->background_url().is_valid())
302 ShutdownAssociatedBackgroundContents(ASCIIToUTF16(extension->id()));
303 break;
304 }
305 default:
306 NOTREACHED();
307 ShutdownAssociatedBackgroundContents(
308 ASCIIToUTF16(
309 Details<UnloadedExtensionInfo>(details)->extension->id()));
310 break;
311 }
312 break;
313
314 case NotificationType::EXTENSION_UNINSTALLED: {
315 // Remove any "This extension has crashed" balloons.
316 const UninstalledExtensionInfo* uninstalled_extension =
317 Details<const UninstalledExtensionInfo>(details).ptr();
318 ScheduleCloseBalloon(uninstalled_extension->extension_id);
319 break;
320 }
321
322 default:
323 NOTREACHED();
324 break;
325 }
326 }
327
328 // Loads all background contents whose urls have been stored in prefs.
LoadBackgroundContentsFromPrefs(Profile * profile)329 void BackgroundContentsService::LoadBackgroundContentsFromPrefs(
330 Profile* profile) {
331 if (!prefs_)
332 return;
333 const DictionaryValue* contents =
334 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
335 if (!contents)
336 return;
337 ExtensionService* extensions_service = profile->GetExtensionService();
338 DCHECK(extensions_service);
339 for (DictionaryValue::key_iterator it = contents->begin_keys();
340 it != contents->end_keys(); ++it) {
341 // Check to make sure that the parent extension is still enabled.
342 const Extension* extension = extensions_service->GetExtensionById(
343 *it, false);
344 if (!extension) {
345 // We should never reach here - it should not be possible for an app
346 // to become uninstalled without the associated BackgroundContents being
347 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a
348 // crash before we could save our prefs.
349 NOTREACHED() << "No extension found for BackgroundContents - id = "
350 << *it;
351 return;
352 }
353 LoadBackgroundContentsFromDictionary(profile, *it, contents);
354 }
355 }
356
LoadBackgroundContentsForExtension(Profile * profile,const std::string & extension_id)357 void BackgroundContentsService::LoadBackgroundContentsForExtension(
358 Profile* profile,
359 const std::string& extension_id) {
360 // First look if the manifest specifies a background page.
361 const Extension* extension =
362 profile->GetExtensionService()->GetExtensionById(extension_id, false);
363 DCHECK(!extension || extension->is_hosted_app());
364 if (extension && extension->background_url().is_valid()) {
365 LoadBackgroundContents(profile,
366 extension->background_url(),
367 ASCIIToUTF16("background"),
368 UTF8ToUTF16(extension->id()));
369 return;
370 }
371
372 // Now look in the prefs.
373 if (!prefs_)
374 return;
375 const DictionaryValue* contents =
376 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
377 if (!contents)
378 return;
379 LoadBackgroundContentsFromDictionary(profile, extension_id, contents);
380 }
381
LoadBackgroundContentsFromDictionary(Profile * profile,const std::string & extension_id,const DictionaryValue * contents)382 void BackgroundContentsService::LoadBackgroundContentsFromDictionary(
383 Profile* profile,
384 const std::string& extension_id,
385 const DictionaryValue* contents) {
386 ExtensionService* extensions_service = profile->GetExtensionService();
387 DCHECK(extensions_service);
388
389 DictionaryValue* dict;
390 contents->GetDictionaryWithoutPathExpansion(extension_id, &dict);
391 if (dict == NULL)
392 return;
393 string16 frame_name;
394 std::string url;
395 dict->GetString(kUrlKey, &url);
396 dict->GetString(kFrameNameKey, &frame_name);
397 LoadBackgroundContents(profile,
398 GURL(url),
399 frame_name,
400 UTF8ToUTF16(extension_id));
401 }
402
LoadBackgroundContentsFromManifests(Profile * profile)403 void BackgroundContentsService::LoadBackgroundContentsFromManifests(
404 Profile* profile) {
405 const ExtensionList* extensions =
406 profile->GetExtensionService()->extensions();
407 ExtensionList::const_iterator iter = extensions->begin();
408 for (; iter != extensions->end(); ++iter) {
409 const Extension* extension = *iter;
410 if (extension->is_hosted_app() &&
411 extension->background_url().is_valid()) {
412 LoadBackgroundContents(profile,
413 extension->background_url(),
414 ASCIIToUTF16("background"),
415 UTF8ToUTF16(extension->id()));
416 }
417 }
418 }
419
LoadBackgroundContents(Profile * profile,const GURL & url,const string16 & frame_name,const string16 & application_id)420 void BackgroundContentsService::LoadBackgroundContents(
421 Profile* profile,
422 const GURL& url,
423 const string16& frame_name,
424 const string16& application_id) {
425 // We are depending on the fact that we will initialize before any user
426 // actions or session restore can take place, so no BackgroundContents should
427 // be running yet for the passed application_id.
428 DCHECK(!GetAppBackgroundContents(application_id));
429 DCHECK(!application_id.empty());
430 DCHECK(url.is_valid());
431 DVLOG(1) << "Loading background content url: " << url;
432
433 BackgroundContents* contents = CreateBackgroundContents(
434 SiteInstance::CreateSiteInstanceForURL(profile, url),
435 MSG_ROUTING_NONE,
436 profile,
437 frame_name,
438 application_id);
439
440 RenderViewHost* render_view_host = contents->render_view_host();
441 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing
442 // startup latency (http://crbug.com/47236).
443 render_view_host->CreateRenderView(frame_name);
444 render_view_host->NavigateToURL(url);
445 }
446
CreateBackgroundContents(SiteInstance * site,int routing_id,Profile * profile,const string16 & frame_name,const string16 & application_id)447 BackgroundContents* BackgroundContentsService::CreateBackgroundContents(
448 SiteInstance* site,
449 int routing_id,
450 Profile* profile,
451 const string16& frame_name,
452 const string16& application_id) {
453 BackgroundContents* contents = new BackgroundContents(site, routing_id, this);
454
455 // Register the BackgroundContents internally, then send out a notification
456 // to external listeners.
457 BackgroundContentsOpenedDetails details = {contents,
458 frame_name,
459 application_id};
460 BackgroundContentsOpened(&details);
461 NotificationService::current()->Notify(
462 NotificationType::BACKGROUND_CONTENTS_OPENED,
463 Source<Profile>(profile),
464 Details<BackgroundContentsOpenedDetails>(&details));
465 return contents;
466 }
467
RegisterBackgroundContents(BackgroundContents * background_contents)468 void BackgroundContentsService::RegisterBackgroundContents(
469 BackgroundContents* background_contents) {
470 DCHECK(IsTracked(background_contents));
471 if (!prefs_)
472 return;
473
474 // We store the first URL we receive for a given application. If there's
475 // already an entry for this application, no need to do anything.
476 // TODO(atwilson): Verify that this is the desired behavior based on developer
477 // feedback (http://crbug.com/47118).
478 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
479 DictionaryValue* pref = update.Get();
480 const string16& appid = GetParentApplicationId(background_contents);
481 DictionaryValue* current;
482 if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), ¤t))
483 return;
484
485 // No entry for this application yet, so add one.
486 DictionaryValue* dict = new DictionaryValue();
487 dict->SetString(kUrlKey, background_contents->GetURL().spec());
488 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name);
489 pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict);
490 prefs_->ScheduleSavePersistentPrefs();
491 }
492
UnregisterBackgroundContents(BackgroundContents * background_contents)493 void BackgroundContentsService::UnregisterBackgroundContents(
494 BackgroundContents* background_contents) {
495 if (!prefs_)
496 return;
497 DCHECK(IsTracked(background_contents));
498 const string16 appid = GetParentApplicationId(background_contents);
499 DictionaryPrefUpdate update(prefs_, prefs::kRegisteredBackgroundContents);
500 update.Get()->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL);
501 prefs_->ScheduleSavePersistentPrefs();
502 }
503
ShutdownAssociatedBackgroundContents(const string16 & appid)504 void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
505 const string16& appid) {
506 BackgroundContents* contents = GetAppBackgroundContents(appid);
507 if (contents) {
508 UnregisterBackgroundContents(contents);
509 // Background contents destructor shuts down the renderer.
510 delete contents;
511 }
512 }
513
BackgroundContentsOpened(BackgroundContentsOpenedDetails * details)514 void BackgroundContentsService::BackgroundContentsOpened(
515 BackgroundContentsOpenedDetails* details) {
516 // Add the passed object to our list. Should not already be tracked.
517 DCHECK(!IsTracked(details->contents));
518 DCHECK(!details->application_id.empty());
519 contents_map_[details->application_id].contents = details->contents;
520 contents_map_[details->application_id].frame_name = details->frame_name;
521 }
522
523 // Used by test code and debug checks to verify whether a given
524 // BackgroundContents is being tracked by this instance.
IsTracked(BackgroundContents * background_contents) const525 bool BackgroundContentsService::IsTracked(
526 BackgroundContents* background_contents) const {
527 return !GetParentApplicationId(background_contents).empty();
528 }
529
BackgroundContentsShutdown(BackgroundContents * background_contents)530 void BackgroundContentsService::BackgroundContentsShutdown(
531 BackgroundContents* background_contents) {
532 // Remove the passed object from our list.
533 DCHECK(IsTracked(background_contents));
534 string16 appid = GetParentApplicationId(background_contents);
535 contents_map_.erase(appid);
536 }
537
GetAppBackgroundContents(const string16 & application_id)538 BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
539 const string16& application_id) {
540 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
541 return (it != contents_map_.end()) ? it->second.contents : NULL;
542 }
543
GetParentApplicationId(BackgroundContents * contents) const544 const string16& BackgroundContentsService::GetParentApplicationId(
545 BackgroundContents* contents) const {
546 for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
547 it != contents_map_.end(); ++it) {
548 if (contents == it->second.contents)
549 return it->first;
550 }
551 return EmptyString16();
552 }
553
554 // static
RegisterUserPrefs(PrefService * prefs)555 void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) {
556 prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents);
557 }
558
AddTabContents(TabContents * new_contents,WindowOpenDisposition disposition,const gfx::Rect & initial_pos,bool user_gesture)559 void BackgroundContentsService::AddTabContents(
560 TabContents* new_contents,
561 WindowOpenDisposition disposition,
562 const gfx::Rect& initial_pos,
563 bool user_gesture) {
564 Browser* browser = BrowserList::GetLastActiveWithProfile(
565 new_contents->profile());
566 if (!browser)
567 return;
568 browser->AddTabContents(new_contents, disposition, initial_pos, user_gesture);
569 }
570