1 /*
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "PageGroup.h"
28
29 #include "Chrome.h"
30 #include "ChromeClient.h"
31 #include "Document.h"
32 #include "Frame.h"
33 #include "GroupSettings.h"
34 #include "IDBFactoryBackendInterface.h"
35 #include "Page.h"
36 #include "PageCache.h"
37 #include "SecurityOrigin.h"
38 #include "Settings.h"
39 #include "StorageNamespace.h"
40
41 #if PLATFORM(CHROMIUM)
42 #include "PlatformBridge.h"
43 #endif
44
45 #ifdef ANDROID
46 #include "DOMWindow.h"
47 #include "FileSystem.h"
48 #endif
49
50 namespace WebCore {
51
getUniqueIdentifier()52 static unsigned getUniqueIdentifier()
53 {
54 static unsigned currentIdentifier = 0;
55 return ++currentIdentifier;
56 }
57
58 // --------
59
60 static bool shouldTrackVisitedLinks = false;
61
PageGroup(const String & name)62 PageGroup::PageGroup(const String& name)
63 : m_name(name)
64 , m_visitedLinksPopulated(false)
65 , m_identifier(getUniqueIdentifier())
66 , m_groupSettings(GroupSettings::create())
67 {
68 }
69
PageGroup(Page * page)70 PageGroup::PageGroup(Page* page)
71 : m_visitedLinksPopulated(false)
72 , m_identifier(getUniqueIdentifier())
73 , m_groupSettings(GroupSettings::create())
74 {
75 ASSERT(page);
76 addPage(page);
77 }
78
~PageGroup()79 PageGroup::~PageGroup()
80 {
81 removeAllUserContent();
82 }
83
84 typedef HashMap<String, PageGroup*> PageGroupMap;
85 static PageGroupMap* pageGroups = 0;
86
pageGroup(const String & groupName)87 PageGroup* PageGroup::pageGroup(const String& groupName)
88 {
89 ASSERT(!groupName.isEmpty());
90
91 if (!pageGroups)
92 pageGroups = new PageGroupMap;
93
94 pair<PageGroupMap::iterator, bool> result = pageGroups->add(groupName, 0);
95
96 if (result.second) {
97 ASSERT(!result.first->second);
98 result.first->second = new PageGroup(groupName);
99 }
100
101 ASSERT(result.first->second);
102 return result.first->second;
103 }
104
closeLocalStorage()105 void PageGroup::closeLocalStorage()
106 {
107 #if ENABLE(DOM_STORAGE)
108 if (!pageGroups)
109 return;
110
111 PageGroupMap::iterator end = pageGroups->end();
112
113 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
114 if (it->second->hasLocalStorage())
115 it->second->localStorage()->close();
116 }
117 #endif
118 }
119
120 #if ENABLE(DOM_STORAGE)
121
clearLocalStorageForAllOrigins()122 void PageGroup::clearLocalStorageForAllOrigins()
123 {
124 if (!pageGroups)
125 return;
126
127 PageGroupMap::iterator end = pageGroups->end();
128 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
129 if (it->second->hasLocalStorage())
130 it->second->localStorage()->clearAllOriginsForDeletion();
131 }
132 }
133
clearLocalStorageForOrigin(SecurityOrigin * origin)134 void PageGroup::clearLocalStorageForOrigin(SecurityOrigin* origin)
135 {
136 if (!pageGroups)
137 return;
138
139 PageGroupMap::iterator end = pageGroups->end();
140 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
141 if (it->second->hasLocalStorage())
142 it->second->localStorage()->clearOriginForDeletion(origin);
143 }
144 }
145
syncLocalStorage()146 void PageGroup::syncLocalStorage()
147 {
148 if (!pageGroups)
149 return;
150
151 PageGroupMap::iterator end = pageGroups->end();
152 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
153 if (it->second->hasLocalStorage())
154 it->second->localStorage()->sync();
155 }
156 }
157
numberOfPageGroups()158 unsigned PageGroup::numberOfPageGroups()
159 {
160 if (!pageGroups)
161 return 0;
162
163 return pageGroups->size();
164 }
165
166 #if defined(ANDROID)
clearDomStorage()167 void PageGroup::clearDomStorage()
168 {
169 if (!pageGroups)
170 return;
171
172 PageGroupMap::iterator end = pageGroups->end();
173
174 for (PageGroupMap::iterator it = pageGroups->begin(); it != end; ++it) {
175 String basePath = "";
176
177 // This is being called as a result of the user explicitly
178 // asking to clear all stored data (e.g. through a settings
179 // dialog. We need a page in the page group to fire a
180 // StorageEvent. There isn't really a correct page to use
181 // as the source (as the clear request hasn't come from a
182 // particular page). One thing we should ensure though is that
183 // we don't try to clear a private browsing mode page as that has no concept
184 // of DOM storage..
185
186 HashSet<Page*> pages = it->second->pages();
187 HashSet<Page*>::iterator pagesEnd = pages.end();
188 Page* page = 0;
189 for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) {
190 Page* p = *pit;
191
192 // Grab the storage location from an arbitrary page. This is set
193 // to the same value on all private browsing and "normal" pages,
194 // so we can get it from anything.
195 if (basePath.isEmpty())
196 basePath = p->settings()->localStorageDatabasePath();
197
198 // DOM storage is disabled in private browsing pages, so nothing to do if
199 // this is such a page.
200 if (p->settings()->privateBrowsingEnabled())
201 continue;
202
203 // Clear session storage.
204 StorageNamespace* sessionStorage = p->sessionStorage(false);
205 if (sessionStorage)
206 sessionStorage->clear(p);
207
208 // Save this page so we can clear local storage.
209 page = p;
210 }
211
212 // If page is still null at this point, then the only pages that are
213 // open are private browsing pages. Hence no pages are currently using local
214 // storage, so we don't need a page pointer to send any events and the
215 // clear function will handle a 0 input.
216 it->second->localStorage()->clear(page);
217 it->second->localStorage()->close();
218
219 // Closing the storage areas will stop the background thread and so
220 // we need to remove the local storage ref here so that next time
221 // we come to a site that uses it the thread will get started again.
222 it->second->removeLocalStorage();
223
224 // At this point, active local and session storage have been cleared and the
225 // StorageAreas for this PageGroup closed. The final sync will have taken
226 // place. All that is left is to purge the database files.
227 if (!basePath.isEmpty()) {
228 Vector<String> files = listDirectory(basePath, "*.localstorage");
229 Vector<String>::iterator filesEnd = files.end();
230 for (Vector<String>::iterator it = files.begin(); it != filesEnd; ++it)
231 deleteFile(*it);
232 }
233 }
234 }
235
removeLocalStorage()236 void PageGroup::removeLocalStorage()
237 {
238 HashSet<Page*> pages = this->pages();
239 HashSet<Page*>::iterator pagesEnd = pages.end();
240 for(HashSet<Page*>::iterator pit = pages.begin(); pit != pagesEnd; ++pit) {
241 Page* p = *pit;
242 for (Frame* frame = p->mainFrame(); frame; frame = frame->tree()->traverseNext())
243 frame->document()->domWindow()->clearDOMStorage();
244 }
245
246 m_localStorage = 0;
247 }
248 #endif // PLATFORM(ANDROID)
249
250 #endif
251
addPage(Page * page)252 void PageGroup::addPage(Page* page)
253 {
254 ASSERT(page);
255 ASSERT(!m_pages.contains(page));
256 m_pages.add(page);
257 }
258
removePage(Page * page)259 void PageGroup::removePage(Page* page)
260 {
261 ASSERT(page);
262 ASSERT(m_pages.contains(page));
263 m_pages.remove(page);
264 }
265
isLinkVisited(LinkHash visitedLinkHash)266 bool PageGroup::isLinkVisited(LinkHash visitedLinkHash)
267 {
268 #if PLATFORM(CHROMIUM)
269 // Use Chromium's built-in visited link database.
270 return PlatformBridge::isLinkVisited(visitedLinkHash);
271 #else
272 if (!m_visitedLinksPopulated) {
273 m_visitedLinksPopulated = true;
274 ASSERT(!m_pages.isEmpty());
275 (*m_pages.begin())->chrome()->client()->populateVisitedLinks();
276 }
277 return m_visitedLinkHashes.contains(visitedLinkHash);
278 #endif
279 }
280
addVisitedLinkHash(LinkHash hash)281 void PageGroup::addVisitedLinkHash(LinkHash hash)
282 {
283 if (shouldTrackVisitedLinks)
284 addVisitedLink(hash);
285 }
286
addVisitedLink(LinkHash hash)287 inline void PageGroup::addVisitedLink(LinkHash hash)
288 {
289 ASSERT(shouldTrackVisitedLinks);
290 #if !PLATFORM(CHROMIUM)
291 if (!m_visitedLinkHashes.add(hash).second)
292 return;
293 #endif
294 Page::visitedStateChanged(this, hash);
295 pageCache()->markPagesForVistedLinkStyleRecalc();
296 }
297
addVisitedLink(const KURL & url)298 void PageGroup::addVisitedLink(const KURL& url)
299 {
300 if (!shouldTrackVisitedLinks)
301 return;
302 ASSERT(!url.isEmpty());
303 addVisitedLink(visitedLinkHash(url.string().characters(), url.string().length()));
304 }
305
addVisitedLink(const UChar * characters,size_t length)306 void PageGroup::addVisitedLink(const UChar* characters, size_t length)
307 {
308 if (!shouldTrackVisitedLinks)
309 return;
310 addVisitedLink(visitedLinkHash(characters, length));
311 }
312
removeVisitedLinks()313 void PageGroup::removeVisitedLinks()
314 {
315 m_visitedLinksPopulated = false;
316 if (m_visitedLinkHashes.isEmpty())
317 return;
318 m_visitedLinkHashes.clear();
319 Page::allVisitedStateChanged(this);
320 pageCache()->markPagesForVistedLinkStyleRecalc();
321 }
322
removeAllVisitedLinks()323 void PageGroup::removeAllVisitedLinks()
324 {
325 Page::removeAllVisitedLinks();
326 pageCache()->markPagesForVistedLinkStyleRecalc();
327 }
328
setShouldTrackVisitedLinks(bool shouldTrack)329 void PageGroup::setShouldTrackVisitedLinks(bool shouldTrack)
330 {
331 if (shouldTrackVisitedLinks == shouldTrack)
332 return;
333 shouldTrackVisitedLinks = shouldTrack;
334 if (!shouldTrackVisitedLinks)
335 removeAllVisitedLinks();
336 }
337
338 #if ENABLE(DOM_STORAGE)
localStorage()339 StorageNamespace* PageGroup::localStorage()
340 {
341 if (!m_localStorage) {
342 // Need a page in this page group to query the settings for the local storage database path.
343 // Having these parameters attached to the page settings is unfortunate since these settings are
344 // not per-page (and, in fact, we simply grab the settings from some page at random), but
345 // at this point we're stuck with it.
346 Page* page = *m_pages.begin();
347 const String& path = page->settings()->localStorageDatabasePath();
348 unsigned quota = m_groupSettings->localStorageQuotaBytes();
349 m_localStorage = StorageNamespace::localStorageNamespace(path, quota);
350 }
351
352 return m_localStorage.get();
353 }
354
355 #endif
356
357 #if ENABLE(INDEXED_DATABASE)
idbFactory()358 IDBFactoryBackendInterface* PageGroup::idbFactory()
359 {
360 // Do not add page setting based access control here since this object is shared by all pages in
361 // the group and having per-page controls is misleading.
362 if (!m_factoryBackend)
363 m_factoryBackend = IDBFactoryBackendInterface::create();
364 return m_factoryBackend.get();
365 }
366 #endif
367
addUserScriptToWorld(DOMWrapperWorld * world,const String & source,const KURL & url,PassOwnPtr<Vector<String>> whitelist,PassOwnPtr<Vector<String>> blacklist,UserScriptInjectionTime injectionTime,UserContentInjectedFrames injectedFrames)368 void PageGroup::addUserScriptToWorld(DOMWrapperWorld* world, const String& source, const KURL& url,
369 PassOwnPtr<Vector<String> > whitelist, PassOwnPtr<Vector<String> > blacklist,
370 UserScriptInjectionTime injectionTime, UserContentInjectedFrames injectedFrames)
371 {
372 ASSERT_ARG(world, world);
373
374 OwnPtr<UserScript> userScript(new UserScript(source, url, whitelist, blacklist, injectionTime, injectedFrames));
375 if (!m_userScripts)
376 m_userScripts.set(new UserScriptMap);
377 UserScriptVector*& scriptsInWorld = m_userScripts->add(world, 0).first->second;
378 if (!scriptsInWorld)
379 scriptsInWorld = new UserScriptVector;
380 scriptsInWorld->append(userScript.release());
381 }
382
addUserStyleSheetToWorld(DOMWrapperWorld * world,const String & source,const KURL & url,PassOwnPtr<Vector<String>> whitelist,PassOwnPtr<Vector<String>> blacklist,UserContentInjectedFrames injectedFrames,UserStyleLevel level,UserStyleInjectionTime injectionTime)383 void PageGroup::addUserStyleSheetToWorld(DOMWrapperWorld* world, const String& source, const KURL& url,
384 PassOwnPtr<Vector<String> > whitelist, PassOwnPtr<Vector<String> > blacklist,
385 UserContentInjectedFrames injectedFrames,
386 UserStyleLevel level,
387 UserStyleInjectionTime injectionTime)
388 {
389 ASSERT_ARG(world, world);
390
391 OwnPtr<UserStyleSheet> userStyleSheet(new UserStyleSheet(source, url, whitelist, blacklist, injectedFrames, level));
392 if (!m_userStyleSheets)
393 m_userStyleSheets.set(new UserStyleSheetMap);
394 UserStyleSheetVector*& styleSheetsInWorld = m_userStyleSheets->add(world, 0).first->second;
395 if (!styleSheetsInWorld)
396 styleSheetsInWorld = new UserStyleSheetVector;
397 styleSheetsInWorld->append(userStyleSheet.release());
398
399 if (injectionTime == InjectInExistingDocuments)
400 resetUserStyleCacheInAllFrames();
401 }
402
removeUserScriptFromWorld(DOMWrapperWorld * world,const KURL & url)403 void PageGroup::removeUserScriptFromWorld(DOMWrapperWorld* world, const KURL& url)
404 {
405 ASSERT_ARG(world, world);
406
407 if (!m_userScripts)
408 return;
409
410 UserScriptMap::iterator it = m_userScripts->find(world);
411 if (it == m_userScripts->end())
412 return;
413
414 UserScriptVector* scripts = it->second;
415 for (int i = scripts->size() - 1; i >= 0; --i) {
416 if (scripts->at(i)->url() == url)
417 scripts->remove(i);
418 }
419
420 if (!scripts->isEmpty())
421 return;
422
423 delete it->second;
424 m_userScripts->remove(it);
425 }
426
removeUserStyleSheetFromWorld(DOMWrapperWorld * world,const KURL & url)427 void PageGroup::removeUserStyleSheetFromWorld(DOMWrapperWorld* world, const KURL& url)
428 {
429 ASSERT_ARG(world, world);
430
431 if (!m_userStyleSheets)
432 return;
433
434 UserStyleSheetMap::iterator it = m_userStyleSheets->find(world);
435 bool sheetsChanged = false;
436 if (it == m_userStyleSheets->end())
437 return;
438
439 UserStyleSheetVector* stylesheets = it->second;
440 for (int i = stylesheets->size() - 1; i >= 0; --i) {
441 if (stylesheets->at(i)->url() == url) {
442 stylesheets->remove(i);
443 sheetsChanged = true;
444 }
445 }
446
447 if (!sheetsChanged)
448 return;
449
450 if (!stylesheets->isEmpty()) {
451 delete it->second;
452 m_userStyleSheets->remove(it);
453 }
454
455 resetUserStyleCacheInAllFrames();
456 }
457
removeUserScriptsFromWorld(DOMWrapperWorld * world)458 void PageGroup::removeUserScriptsFromWorld(DOMWrapperWorld* world)
459 {
460 ASSERT_ARG(world, world);
461
462 if (!m_userScripts)
463 return;
464
465 UserScriptMap::iterator it = m_userScripts->find(world);
466 if (it == m_userScripts->end())
467 return;
468
469 delete it->second;
470 m_userScripts->remove(it);
471 }
472
removeUserStyleSheetsFromWorld(DOMWrapperWorld * world)473 void PageGroup::removeUserStyleSheetsFromWorld(DOMWrapperWorld* world)
474 {
475 ASSERT_ARG(world, world);
476
477 if (!m_userStyleSheets)
478 return;
479
480 UserStyleSheetMap::iterator it = m_userStyleSheets->find(world);
481 if (it == m_userStyleSheets->end())
482 return;
483
484 delete it->second;
485 m_userStyleSheets->remove(it);
486
487 resetUserStyleCacheInAllFrames();
488 }
489
removeAllUserContent()490 void PageGroup::removeAllUserContent()
491 {
492 if (m_userScripts) {
493 deleteAllValues(*m_userScripts);
494 m_userScripts.clear();
495 }
496
497 if (m_userStyleSheets) {
498 deleteAllValues(*m_userStyleSheets);
499 m_userStyleSheets.clear();
500 resetUserStyleCacheInAllFrames();
501 }
502 }
503
resetUserStyleCacheInAllFrames()504 void PageGroup::resetUserStyleCacheInAllFrames()
505 {
506 // Clear our cached sheets and have them just reparse.
507 HashSet<Page*>::const_iterator end = m_pages.end();
508 for (HashSet<Page*>::const_iterator it = m_pages.begin(); it != end; ++it) {
509 for (Frame* frame = (*it)->mainFrame(); frame; frame = frame->tree()->traverseNext())
510 frame->document()->updatePageGroupUserSheets();
511 }
512 }
513
514 } // namespace WebCore
515